Annotation自定義註解

前言:

Sometimes there is no next time, no time-outs, no second chances; sometimes it's now or never.

對於註解來說,其實就是用來打標記用的,在別的地方可以根據特殊的標記得到標記的東西(屬性、類、方法等),對於這大家應該非常熟悉了,很多第三方框架使用也巨多,比如Dagger、ButterKnife等等,這些第三方註解框架,省略了findviewById(),不禁要問,難道真的不需要這行findviewById()代碼就能在xml中得到控件實例嗎?答案很明確,肯定不是如此。本篇文章就會給出答案,主要介紹自定義註解相關的知識,模仿實現findviewById功能。

1、自定義註解回顧

在開始實現 findviewById 功能之前,還是複習一下怎麼自定義註解,以及註解有啥作用吧。

有這樣一種場景:

image

主要工作是,要寫一些註解類來標識SxtStudent類,然後通過註解把類中的信息跟表對應起來。比如上面SxtStudent類中,id爲int類型,對應SQL中int(10);name爲String類型,對應SQL中的varchar(10).。方式就是通過註解對類中的信息進行標註,寫一箇中間程序獲取這個類中的註解信息,把註解解析出來,然後恰好可以作爲SQL語句中拼接。

首先定義JavaBean類:

public class YdlStudent {  
 private int id;
 private String studentName;
 private int age;

 public int getId() {
   return id;
 }

 public void setId(int id) {
   this.id = id;
 }

 public String getStudentName() {
   return studentName;
 }

 public void setStudentName(String studentName) {
   this.studentName = studentName;
 }

 public int getAge() {
   return age;
 }

 public void setAge(int age) {
   this.age = age;
 }
}

然後定義類與表對應轉換的註解:

/**
* 針對表的註解
* 代表類與表之間對應轉換
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
public @interface YdlTable {
 String value();
}

因爲表只需要一個表名即可,所以註解中只需要一個信息,這裏使用String value();標識。

這個時候就可以對類進行註解了,這裏對類的註解可以通過反射技術獲取註解信息,然後作爲創建的表名稱。

image

然後還可以對屬性添加註解,定義與屬性關聯的註解類:

/**
* 針對屬性的註解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.FIELD})
public @interface YdlFiled {
 String columName();//列名
 String type();//數據類型
 int length();//年齡
}

這裏註解裏面的內容是根據Student類和SQL來寫的,SQL中的列名稱定義爲String columName();SQL中的數據類型爲String type();SQL中的數據類型佔的長度定義爲int length();

然後在YdlStudent類中對屬性添加註解:

image

這裏

@YdlFiled(columName = "id",type = "int",length = 10)

private int id;

理解爲:YdlStudent中屬性id對應表中的字段id這一列,sql中的類型爲int,int長度爲10。

@YdlFiled(columName = "sname",type = "vachar",length = 10)

private String studentName;

理解爲:YdlStudent中屬性studentName對應表中的字段sname這一列,sql中的類型爲vachar,vachar長度爲10。

這樣標識以後,就可以寫一個解析程序來解析YdlStudent類,獲取註解相關信息來創建表了:

public class Main {
 public static void main(String[] args) {
   Class clazz = YdlStudent.class;
   Annotation[] annotations = clazz.getAnnotations();
   for (Annotation annotation : annotations) {
     // 獲取所有的【類】註解
     System.out.println(annotation);
   }

   // 或得類的指定註解
   YdlTable table = (YdlTable) clazz.getAnnotation(YdlTable.class);
   // table.value()直接獲取註解內容名稱---@YdlTable(value = "tb_student")//可以通過反射讀取這裏,然後創建對應的表
   System.out.println(table.value());

   try {
     // 根據名稱獲取屬性對象
     Field field = clazz.getDeclaredField("studentName");
     // 獲取該屬性上面的註解
     YdlFiled ydlFiled = field.getAnnotation(YdlFiled.class);
     // 獲取註解屬性的內容。
     System.out.println(ydlFiled.columName() + "---" + ydlFiled.type()
         + "---" + ydlFiled.length());

     // 同理可以獲取 YdlStudent 類中 id、age所對應的註解信息

   } catch (Exception e) {
     e.printStackTrace();
   }

   // 根據上方獲取到的表名、屬性(字段)信息等拼接Sql語句。
 }
}

這裏分段來講:

   Class clazz = YdlStudent.class;
   Annotation[] annotations = clazz.getAnnotations();
   for (Annotation annotation : annotations) {
     // 獲取所有的【類】註解
     System.out.println(annotation);
   }

這裏表示通過反射獲取所有的類註解,因類註解只有一個:

image

所以打印結果爲:@itydl03.YdlTable(value=tb_student)

   // 或得類的指定註解
   YdlTable table = (YdlTable) clazz.getAnnotation(YdlTable.class);
   // table.value()直接獲取註解內容名稱---@YdlTable(value = "tb_student")//可以通過反射讀取這裏,然後創建對應的表
   System.out.println(table.value());

表示通過反射獲取類的指定註解,clazz.getAnnotation(YdlTable.class);表示指定獲取YdlTable類的註解。然後table.value()直接獲取註解內容名稱---@YdlTable(value = "tb_student")。獲取的內容就是tb_student,打印結果:

tb_student

try {
     // 根據名稱獲取屬性對象
     Field field = clazz.getDeclaredField("studentName");
     // 獲取該屬性上面的註解
     YdlFiled ydlFiled = field.getAnnotation(YdlFiled.class);
     // 獲取註解屬性的內容。
     System.out.println(ydlFiled.columName() + "---" + ydlFiled.type()
         + "---" + ydlFiled.length());

     // 同理可以獲取 YdlStudent 類中 id、age所對應的註解信息

   } catch (Exception e) {
     e.printStackTrace();
   }

clazz.getDeclaredField("studentName");表示根據名稱獲取屬性對象,返回值得到一個Field field 表示屬性 。 在YdlStudent類中  private String studentName; 屬性對應的註解爲

@YdlFiled(columName = "sname",type = "vachar",length = 10)

field.getAnnotation(YdlFiled.class);就表示獲上邊屬性YdlFiled註解類中的@YdlFiled(columName = "sname",type = "vachar",length = 10)的所有註解信息;返回值爲YdlFiled類型。他可以理解爲columName = "sname",type = "vachar",length = 10的註解信息的javaBean。最後打印:

sname---vachar---10

最後再把所有log給出:

image
2、仿造findviewById()

有了上邊的基礎回顧,下面就不寫那麼細緻了。

首先定義一個註解類,ViewById:

@Target(value={ElementType.FIELD})//FIELD表示的是成員變量級別可以使用該註解
@Retention(RetentionPolicy.RUNTIME)//RUNTIME級別可以被反射讀取註解,都是運行時
public @interface ViewById {
   int value();
}

然後在MainActivity中使用該註解:

public class MainActivity extends AppCompatActivity {

   @ViewById(R.id.tv)
   private TextView mTextView;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       mTextView.setText("自定義註解");
   }
}

看着好像挺熟悉的,跟使用第三方框架差不多,但是運行程序肯定報錯。這是因爲MainActivity類跟自定義註解壓根就沒有什麼連接關係。那麼接下來就建立連接關係。而這個中介就是反射。

自定義ViewUtils類:

public class ViewUtils {
   public static void inject(Activity activity) {
       // 1.獲取所有的屬性
       Field[] fields = activity.getClass().getDeclaredFields();
       // 2.過濾關於 ViewById 屬性
       for (Field field : fields) {
           //獲取註解封裝---ViewById類型的註解。相當於對ViewById所標識屬性的封裝類
           ViewById viewById =  field.getAnnotation(ViewById.class);
           if(viewById != null){
               // 3.findViewById
               View view = activity.findViewById(viewById.value());
               field.setAccessible(true);
               try {
                   // 4.反射注入
                   // activity 屬性所在類,view 代表的是屬性的值
                   field.set(activity,view);
               } catch (IllegalAccessException e) {
                   e.printStackTrace();
               }
           }
       }
   }
}

代碼註釋非常詳細了,主要功能就是反射解析註解,然後在這裏進行了findviewById操作獲取View,最後再注入到屬性對象裏面field.set(activity,view);

此時需要在MainActivity中加入如下代碼:

ViewUtils.inject(this);

表示注入當前的MainActivity,只有注入了才能使用自定義的註解“框架”。

爲了更直觀,把MainActivity中代碼稍微修改如下:

public class MainActivity extends AppCompatActivity {

   //註解屬性,解析類就是解析這裏帶註解的屬性
   @ViewById(R.id.tv)
   private TextView mTextView;

   @ViewById(R.id.tv2)
   private TextView mTextView2;

   //非註解屬性
   private int age;
   private String name;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);

       ViewUtils.inject(this);

       mTextView.setText("自定義註解");

       mTextView2.setText("我是SuperMan");
   }
}

其中private int age;和private String name;兩個屬性雖然在註解解析工具類會獲取到,但是我們已經對其做了過濾處理。此時運行程序:

image

到此爲止,註解相關的知識就講解完了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章