重走JAVA程式設計之路(二)註解

東陸之滇發表於2019-03-07

重新梳理技術棧知識點

註解是什麼?

註解是一種特殊的介面,註解繼承自 java.lang.annotation.Annotation

/**
The common interface extended by all annotation types. 
Note that an interface that manually extends this one does not define an annotation type. 
Also note that this interface does not itself define an annotation type. 
More information about annotation types can be found in section 9.6 of The Java™ Language Specification. 
The reflect.AnnotatedElement interface discusses compatibility concerns when evolving an annotation type from being non-repeatable to being repeatable.
*/
public interface Annotation {
    ...
}
複製程式碼

Annotation介面的文件描述:

  • 所有註解型別都會繼承 Annotation 介面
  • 手工顯示繼承 Annotation 介面,不會定義成一個註解型別
  • Annotation 本身並不會定義成一個註解型別

編寫一個註解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Demo {
}

複製程式碼

編譯: javac .\Demo.java 檢視位元組碼: javap .\Demo.class:

       Compiled from "Demo.java"
       public interface org.byron4j.cookbook.javacore.anno.Demo extends java.lang.annotation.Annotation {
複製程式碼

通過位元組碼,我們可以看到註解繼承自 Annotation 介面。

註解的用處

我們要學習一個東西,肯定要知道它的作用。相信沒有 Java 程式猿不知道 Spring 框架的,Spring 框架中定義了大量的註解,基於註解,我們可以做到幾乎零配置的開發商業專案,如 SpringBoot 就將原來 Spring 使用 XML 進行配置全部轉換為使用註解達到相同的功效。 如以下是部分註解:

  • @RestController
  • @RequestMapping
  • @Configuration
  • @SpringBootApplication
  • @ConfigurationProperties

瞭解註解

註解可以分為 元註解內建註解使用者自定義註解

元註解

所謂元註解就是,一種基本註解,可以應用在其它的註解上。可以這樣理解,元註解是用於標記註解的註解。

元註解有:

  • java.lang.annotation.Retention
  • java.lang.annotation.Target
  • java.lang.annotation.Documented
  • java.lang.annotation.Inherited
  • java.lang.annotation.Repeatable
  • java.lang.annotation.Native

@Retention

Retention 是保留的意思,表明註解產生的時間範圍。其值是 java.lang.RetentionPolicy列舉。

  • RetentionPolicy.SOURCE : 只在原始碼級別保留有用,在編譯期就丟棄了
  • RetentionPolicy.CLASS : 在編譯期保留有效,在執行期(JVM中)開始丟棄;這是預設的保留策略
  • RetentionPolicy.RUNTIME : 在編譯期、執行其都保留有效,所以可以在反射中使用
@Retention(RetentionPolicy.RUNTIME)
public @interface Demo {
}
複製程式碼

@Documented

@Documented 表明註解的型別將會包含到Javadoc中去。

@Target

@Target 標明註解使用約束的應用上下文,是陣列形式,可以標記在多個範圍中使用。值由 java.lang.annotation.ElementType指定。

java.lang.annotation.ElementType 的可用值如下:

  • TYPE : 類、介面、註解、列舉的宣告中
  • FIELD : 成員變數,包含列舉的常量的宣告中
  • METHOD : 方法的宣告中
  • PARAMETER : 正式的引數的宣告中
  • CONSTRUCTOR : 構造器的宣告中
  • LOCAL_VARIABLE : 區域性變數的宣告中
  • ANNOTATION_TYPE : 註解型別的宣告中
  • PACKAGE : 包的宣告中
  • TYPE_PARAMETER : 型別引數的宣告中(since JDK8)
  • TYPE_USE : 型別的使用(since JDK8)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
public @interface Demo {
}

複製程式碼

@Inherited

Inherited 字面意思是繼承得意思,```@Inherited`` 這個註解將會被自動被繼承。 要求子類沒有被任何註解標記。 只會在extends時才有效,實現介面是不生效的。給個例子解釋下:

@Inherited
public @interface Demo {
}

@Demo
public class SuperClass{
    
}

public class ChildClass extends SuperClass{
    
}
複製程式碼

父類 SuperClass 被註解 @Demo 標記了,子類 ChildClass 繼承了父類 SuperClass 且子類沒有被任何註解標記過,則子類會繼承父類的被元註解@Inherited標記的註解 @Demo。

等效於:

@Demo
public class ChildClass extends SuperClass{
    
}
複製程式碼

@Repeatable

該元註解標明宣告的註解是可以多次使用的,@Repeatable 屬性要求是 註解的class。 以下是一個 Demo註解,表示使用者角色的等級,SSS---》B 共5個等級。

注意: 使用了 @Repeatable 標記了 Demo註解,指定了 Role.class 作為屬性值,並且 Role 註解必須屬性名為 value, 且型別為 Demo 陣列形式。

示例如下:

@Repeatable(Role.class)
public @interface Demo {
    String role();
}


public @interface Role {
    Demo[] value();
}


public @interface Td {
}

@Demo(role="SSS")
@Demo(role="SS")
@Demo(role="S")
@Demo(role="A")
@Demo(role="B")
public class FK1 {
}


@Demo(role="SSS")
@Demo(role="SS")
@Demo(role="S")
@Demo(role="A")
@Demo(role="B")
@Td // 錯誤
@Td // 錯誤  Td註解不能重複出現
public class FK1 {
}
複製程式碼
  • 沒有被@Repeatable標記的註解不能多次出現。

註解的屬性

註解只有變數(屬性),沒有方法, 註解的屬性是以 無參方法的形式 宣告的。

public @interface Demo {
    String role();
}
複製程式碼

@Demo 註解宣告瞭一個屬性 role。

屬性的預設值

屬性後面使用 default關鍵字可以給屬性設定預設值。

public @interface Demo {
    String role() default "B";
}
複製程式碼

註解的使用

如果只有一個名為 value 的屬性,則使用註解的時候可以直接在圓括號寫屬性值。

public @interface Due {
    String value();
}

@Due("Hi")
public class FK1 {
}

// 等效於

@Due(value="Hi")
public class FK1 {
}
複製程式碼

沒有屬性的註解,使用時僅僅標記下即可。

public @interface Due {
}

@Due // 僅僅標記下即可
public class FK1 {
}
複製程式碼

Java 內建註解

  • @Deprecated : 表示廢棄,在編譯期會發出警告。
public class AnnoDemo {
    @Deprecated
    private static void sayHello(){

    }

    public static void main(String[] args){
        AnnoDemo.sayHello();
    }
}
複製程式碼
  • @FunctionalInterface : 函式式介面:一個具有一個方法的普通介面。

  • @Override : 實現類要重寫父類或者介面的方法

  • @SafeVarargs : 引數安全型別註解,告訴開發者不要用引數做一些不安全的操作

  • @SuppressWarnings : 阻止編譯器發出告警,比如呼叫了使用了 @Deprecated 標記的方法編譯器會發出警告,可以使用 @SuppressWarnings 壓制警告

    可以通過 javac -X 命令檢視可以壓制的警告值:

    C:\Users\BYRON.Y.Y>javac -X
      -Xlint                     啟用建議的警告
      -Xlint:{all,auxiliaryclass,cast,classfile,deprecation,dep-ann,divzero,empty,fallthrough,finally,options,overloads,overrides,path,processing,rawtypes,serial,static,try,unchecked,varargs,-auxiliaryclass,-cast,-classfile,-deprecation,-dep-ann,-divzero,-empty,-fallthrough,-finally,-options,-overloads,-overrides,-path,-processing,-rawtypes,-serial,-static,-try,-unchecked,-varargs,none} 啟用或禁用特定的警告
    複製程式碼

    @SuppressWarnings 的部分值介紹:

    • all : @SuppressWarnings("all") ,會壓制所有的警告
    • cast : 壓制類造型轉換警告
    • deprecation : 壓制廢棄的警告,比如可能使用了 @Deprecated
    • divzero : 壓制除數為0的警告
    • unchecked : 壓制沒有指定泛型的集合表示式
    • fallthrough : 壓制 switch警告,比如某個case沒有break語句
    • serial : 壓制 實現 Serializable 介面但是沒有宣告 serialVersionUID 屬性的警告
    • finally :壓制沒有正確使用finally的警告,比如沒有捕獲異常,編譯器會發出警告:
      @SuppressWarnings("finally")
      public String finallyTest(String str)
      {
          try
          {
              str+=".";
          }
          finally
          {
              return str.toUpperCase();
          }
      }
    複製程式碼
    • overrides : 壓制沒有覆蓋父類的方法的警告

讓註解有用武之地

註解僅僅是用作標記,要想它真實發揮作用,式利用Java反射機制編寫註解解析器,用作業務需求。

註解與反射(java.lang.reflect包下)

  • 可以通過 java.lang.reflect.ClassisAnnotationPresent()得知是否存在註解。
  • 可以通過 <T extends Annotation> T getAnnotation(Class<T> annotationClass)方法獲取註解物件
  • 可以通過 Annotation[] getAnnotations()方法獲取註解列表
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface ByronAnno {
    String value() default "ok";
}

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface ByronAnno {
    String value() default "ok";
}


複製程式碼

執行輸出:註解值為:類

註解生產案例

最後以一個生產案例的使用,來結束對註解的介紹。

案例: http介面中,請求引數是字串形式,將請求引數轉換為請求實體類。對引數進行校驗時,需要檢查某些欄位是否為空,以及整型數值的大小校驗。

ValidateVal 註解類

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ValidateVal {

	String length() default "";

	boolean isBeNull() default false;

	boolean isNotcheckDecimal() default false;// 驗證小數

	int isJudgeLength() default 0; // 0 :不判斷 1:定值 2:區間

	int minLength() default 0;

	int maxLength() default 20;

	int minValue() default 0;

	int maxValue() default Integer.MAX_VALUE;

	String expression() default "";

	String errorMsg() default "";

	int neeDivided() default 1; // 是否需要倍數
	//是否校驗數字,是=true,否=false
	boolean isCheckNumber() default false;

}

複製程式碼

用於解析 @ValidateVal 註解的解析方法

protected <T> T validateAndConvert(String param, Class<T> cla) throws Exception {

		T t = JSONUtils.json2Object(param, cla);
		if (t == null) {
			// 丟擲異常
		}
		Field[] fields = t.getClass().getDeclaredFields();
		Field[] parentFields = t.getClass().getSuperclass().getDeclaredFields();
		
		List<Field> allFields = Lists.newArrayList(fields);
		allFields.addAll(Arrays.asList(parentFields));
		
		for (Field f : allFields) {
			f.setAccessible(true);
			ValidateVal an = f.getAnnotation(ValidateVal.class);
			String fieldName = f.getName();
			if (an == null) {
				String value = String.valueOf(f.get(t));
				value = value.trim();
				if (f.getType().equals(String.class)) {
					f.set(t, value);
				}
				if (value == null || value.equals("") || value.equals("null")) {
					// 丟擲異常
				}
			} else {
				if (f.getType().equals(String.class)) {
					String value = null;

					if (f.get(t) != null) {
						value = String.valueOf(f.get(t));
						value = value.trim();
						f.set(t, value);
					}

					if (!an.isBeNull()) {
						if (value == null || value.equals("") || value.equals("null")) {
							
							// 丟擲異常
						}
					} else {// 為空串置為null
						if (value == null || value.equals("") || value.equals("null")) {
							f.set(t, null);
						}
					}

					if (!an.expression().equals("")) {
						Pattern pattern = Pattern.compile(an.expression());
						Matcher matcher = pattern.matcher(value);
						if (!matcher.matches()) {
							// 丟擲異常
						}
					}

					if (an.isJudgeLength() == 1) { // 定值
						String[] lengthArr = an.length().split(",");
						boolean in = false;
						for (int i = 0; i < lengthArr.length; i++) {
							if (value.length() == Integer.parseInt(lengthArr[i])) {
								in = true;
							}
						}
						if (!in) {
							// 丟擲異常
						}
					} else if (an.isJudgeLength() == 2) {
						int min = an.minLength();
						int max = an.maxLength();
						if (value.length() < min || value.length() > max) {
							// 丟擲異常
						}
					}
				} else if (f.getType().equals(Integer.class)) {
					
					
					if (f.get(t) == null) {
						if (an.isBeNull()) {
							f.set(t, null);
							continue;
						} else {
							// 丟擲異常
						}
					}
					Integer value = Integer.valueOf(f.get(t).toString());
					if (an.neeDivided() != 1 && value % an.neeDivided() != 0) {
						// 丟擲異常
					}

					if (value < an.minValue() || value > an.maxValue()) {
						// 丟擲異常
					}
				}
			}
		}
		return t;
	}
複製程式碼

參考資料:

  • SuppressWarnings values: http://blog.bangbits.com/2008/05/suppresswarnings-annotations.html

相關文章