重走JAVA編程之路(二)註解

從新梳理技術棧知識點html

註解是什麼?

註解是一種特殊的接口,註解繼承自 java.lang.annotation.Annotationjava

/** 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接口的文檔描述:express

  • 全部註解類型都會繼承 Annotation 接口
  • 手工顯示繼承 Annotation 接口,不會定義成一個註解類型
  • Annotation 自己並不會定義成一個註解類型

編寫一個註解

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

複製代碼

編譯: javac .\Demo.java 查看字節碼: javap .\Demo.class:json

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

瞭解註解

註解能夠分爲 元註解內置註解用戶自定義註解app

元註解

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

元註解有:ide

  • 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
相關文章
相關標籤/搜索