一、認(rèn)識注解
一直準(zhǔn)備寫一波關(guān)于學(xué)習(xí)Java SE方面的文章疏橄,但是從我大學(xué)第一次寫博客開始占拍,就比較零零散散,但是無論如何JavaSE還是基礎(chǔ)捎迫,還是需要認(rèn)真打好基礎(chǔ)晃酒。
廢話不多說,Java注解在開發(fā)中可是經(jīng)常遇到窄绒,Android的框架中都有注解的身影贝次,例如Retrofit、butterknife框架彰导,到處都是注解蛔翅,那么注解到底是神馬呢?如何使用注解位谋?我們怎樣自定義一個注解呢山析?別急,今天不僅有基礎(chǔ)知識掏父,博客最后還會包含一個小項目帶大家實(shí)踐實(shí)踐笋轨,試試手。
首先帶帶大家看看什么是注解:
其中@Override就是一個常見的注解赊淑,它代表的是重寫的意思翩腐,由于它是位于一個方法之上,所以他重寫了該方法膏燃,至于重寫父類什么并不是我們關(guān)心的重點(diǎn)茂卦,再來看一個:
@TargetApi這個注解是安卓開發(fā)中用來標(biāo)識該方法是哪個安卓版本的Api,其中小括號里的參數(shù)即代表哪個版本的Android系統(tǒng)API组哩,可以看到等龙,@TargetApi這個注解也是作用于方法上的处渣。類似于這種格式語法的注解有很多種。
好的蛛砰,那么學(xué)習(xí)注解有什么好處呢罐栈?
我列出以下三點(diǎn):
1.能夠讀懂別人寫的代碼,特別是框架相關(guān)的代碼泥畅!
2.讓編程更加簡潔荠诬,代碼更加清晰
3.讓別人高看一眼,裝逼位仁!
好了柑贞,直接上注解的定義:
注解:Java提供了一種原程序中的元素關(guān)聯(lián)任何信息和任何元數(shù)據(jù)的途徑和方法。
(Annotation(注解)是JDK1.5及以后版本引入的)
注解的分類有以下幾種:
按照運(yùn)行機(jī)制來分(作用時間):
1.源碼注解 //注解只在源碼中存在聂抢,編譯成.class文件就不存在了
2.編譯時的注解钧嘶、 //注解在源碼和.class文件中都存在,只在編譯時起作用琳疏。@Override
3.運(yùn)行時的注解 //在運(yùn)行階段還起作用有决,甚至?xí)绊戇\(yùn)行邏輯的注解。
按照來源來分
1.來自JDK的注解
2.來自第三方的注解‘
3.我們自己定義的注解
二空盼、創(chuàng)建注解
現(xiàn)在我們來看看一個自定義注解的語法要求:
// 我們自定義一個注解的基本格式
@Target({ElementType.METHOD,ElementType.TYPE}) //可以指定被注解的類型
@Retention(RetentionPolicy.RUNTIME) //注解類型:運(yùn)行時书幕、編譯時
@Inherited //是否允許子類繼承注解 接口不起作用 需要是類繼承
@Documented
//如果注解只有一個成員,則這個成員名字必須取名為value()揽趾,方便在使用時的默認(rèn)參數(shù)
public @interface Description {
//成員必須無參 無異常方式聲明
String desc();
//成員類型是受限的按咒,合法類型:String Class Annotation Enumeration
String author();
int age() default 18;
}
可以看到這就是一個自定義注解的大體結(jié)構(gòu)。
@interface 自定義注解的關(guān)鍵字
下面四個是元注解:
@Target 指定作用域 但骨,參數(shù)可以為構(gòu)造方法励七、字段、局部變量奔缠、方法掠抬、包、參數(shù)校哎、類或接口Type
@Retention 指定注解類型
@Inherited 是否允許繼承
@Documented 生成doc說明文檔
然后自定義注解 public @interface 注解名 {
定義各種方法
}
大括號里面成員必須符合:
成員類型是受限制的两波,只能是String、Class闷哆、Annotation腰奋、Enumeration類型
成員必須無參數(shù)且無異常方式聲明。
可以指定default為成員指定一個默認(rèn)值
注意:如果注解只有一個成員的話抱怔,則成員名必須為value(),方便在使用時忽略成員名和賦值號
注解可以沒有成員劣坊,則這個注解稱為標(biāo)識注解!
好了屈留,這樣一個叫Description的注解被我們create出來了局冰,而且是可以在方法上和類上都能使用這個注解测蘑,我們把它聲明為是一個運(yùn)行時注解,不允許子類繼承康二,可以生成doc文檔碳胳。
注意:這個注解里面含有三個方法。
三沫勿、使用注解
那么我們怎么使用這個注解呢挨约?
使用注解的語法格式:
@<注解名>(<成員名1>=<成員值1>,<成員名2>=<成員值2>,....)
拿上面注解的舉個例子:
@Description(desc="I am eyeColor",author="Mooc boy",age=18)
public String eyeColor() {
return "red";
}
可以看到這個使用自定義注解是作用于方法上产雹。
Ok诫惭,create注解和使用自定義注解的都已經(jīng)完成,還是很簡單的洽故。但是我們這個注解還沒有實(shí)際的作用,我們可以利用反射來完成注解的邏輯功能盗誊。
解析注解
為了讓我們自定義的注解能有“實(shí)際意義”时甚,我們可以通過解析自定義注解,來讓我們創(chuàng)建的代碼能夠控制邏輯哈踱。
通過反射獲取類,函數(shù),或成員上的運(yùn)行時注解信息,從而實(shí)現(xiàn)動態(tài)控制程序運(yùn)行時的邏輯
為了便于理解荒适,這里我舉個簡單完整的例子:
1.Create一個名叫Guikai注解
import java.lang.annotation.*;
@Target({ElementType.METHOD,ElementType.TYPE}) //元注解 注解作用類型
@Retention(RetentionPolicy.RUNTIME) //指定注解的類型 運(yùn)行時
@Documented
@Inherited
public @interface Guikai {
String Name();
String Num();
String Class();
}
2.新建一個類,使用這個注解
@Guikai(Name = "這是一個類上的注解",Num = "2014911006",Class = "計本一班")
public class Person {
@Guikai(Name = "這是一個方法上的注解",Num = "2014911006",Class = "計本一班")
public String name() {
return null;
}
public int age() {
return 0;
}
}
可以看到我們在方法和類上都加上我們自定義的注解
3.解析注解代碼實(shí)例
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
/**
* 解析注解:
* 通過反射獲取類,函數(shù),或成員上的運(yùn)行時注解信息,從而實(shí)現(xiàn)動態(tài)控制程序運(yùn)行時的邏輯
*/
public class Sample {
public static void main(String[] args) {
try {
//獲取類對象开镣,判斷類上面是否有注解
Class c = Class.forName("annotation.Person");
boolean isExist = c.isAnnotationPresent(Guikai.class);
if (isExist) {
Guikai d = (Guikai) c.getAnnotation(Guikai.class);
System.out.println(d.Name() + d.Num() + d.Class());
}
//接下來獲得方法上的注解
Method[] ms = c.getMethods();
for (Method item : ms) {
//反射獲取方法列表刀诬,然后遍歷每個方法,判斷是否有注解
boolean isMExit = item.isAnnotationPresent(Guikai.class);
if (isMExit) {
Guikai g = item.getAnnotation(Guikai.class);
System.out.println(g.Name() + g.Num() + g.Class());
}
}
//第二種方法
for (Method m : ms) {
Annotation[] as = m.getAnnotations();
for (Annotation a : as) {
if (a instanceof Guikai) {
Guikai d = (Guikai) a;
System.out.println(d.Name() + d.Num() + d.Class());
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
輸出結(jié)果:
這是一個類上的注解2014911006計本一班
這是一個方法上的注解2014911006計本一班
這是一個方法上的注解2014911006計本一班
這樣我們就通過反射邪财,獲取到類上面的注解陕壹,然后打印出guikai注解上的參數(shù)!
接下來树埠,我將附上一個生成sql語句的例子糠馆,讓大家感受一下注解的強(qiáng)大!
四怎憋、Demo實(shí)戰(zhàn)
注解的基礎(chǔ)知識我們講的差不多了又碌,接下來來一個注解實(shí)戰(zhàn):
項目取自一個公司的持久層框架,用來代替Hibernate的解決方法绊袋,核心代碼就是通過注解來實(shí)現(xiàn)毕匀,
當(dāng)然一個商業(yè)級的項目過于復(fù)雜,我們這里只是抽離出核心代碼出來癌别。
需求:
1.有一張用戶表皂岔,字段包括用戶ID、用戶名展姐、昵稱凤薛、年齡姓建,性別,所在城市缤苫,郵箱速兔,手機(jī)號。
2.方便對每一個字段或字段的組合條件進(jìn)行檢索活玲,并打印出SQL語句涣狗。
3.使用的方式需要足夠簡單!
首先我們需要一個Bean類(對應(yīng)數(shù)據(jù)表字段)
Filter.java文件:
package com.example.dao;
@Table("user")
public class Filter {
@Column("id")
private int id;
@Column("user_name")
private String userName;
@Column("nick_name")
private String nickName;
@Column("age")
private int age;
@Column("city")
private String city;
@Column("email")
private String email;
@Column("mobile")
private String mobile;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
}
在Bean類的類名和方法上舒憾,有兩個我們自定義的注解镀钓,分別為Column、Table镀迂,其只有一個成員
Column.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIEID})
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
String value();
}
Table.java
package com.example.dao;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIEID})
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
String value();
}
Test.java測試類:
package com.example.dao;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) {
Filter f1 = new Filter();
f1.setId(10); //查詢?yōu)?0的用戶
Filter f2 = new Filter();
f2.setUserName("lucy"); //模糊查詢用戶名為lucy的用戶
Filter f3 = new Filter();
f3.setEmail("guikai@qq.com,zh@163.com,77777@qq.com");//查詢郵箱為其中任意一個的用戶
String sql1 = query(f1);
String sql2 = query(f2);
String sql3 = query(f3);
System.out.println(sql1);
System.out.println(sql2);
System.out.println(sql3);
}
private static String query(Filter f) {
StringBuffer sb = new StringBuffer();
//1.獲取到class
Class c = f.getClass();
//2.獲取到table的名字
boolean exists = c.isAnnotationPresent(Table.class);
if (!exists) {
return null;
}
Table table = (Table) c.getAnnotation(Table.class);
String tableName = table.value();
sb.append("Select * from ").append(tableName).append(" where 1=1");
//3.遍歷所有的字段
Field[] fArray = c.getDeclaredFields();
for (Field field:fArray) {
//4.處理每個字段對應(yīng)的sql
//4.1 拿到字段的名字
boolean fExists = field.isAnnotationPresent(Column.class);
if (!fExists) {
continue;
}
Column column = field.getAnnotation(Column.class);
String columnName = column.value();
//4.2 拿到字段的值
String filedName = field.getName();
String getMethodName = "get" + filedName.substring(0, 1).toUpperCase()
+ filedName.substring(1);
Object fieldValue = null;
try {
Method getMethod = c.getMethod(getMethodName);
fieldValue = (Object) getMethod.invoke(f);
} catch (Exception e) {
e.printStackTrace();
}
//4.3 拼裝sql
if (fieldValue==null ||
(fieldValue instanceof Integer && (Integer)fieldValue==0)) {
continue;
}
sb.append(" and ").append(filedName);
if (fieldValue instanceof String) {
if (((String) fieldValue).contains(",")){
String[] values = ((String) fieldValue).split(",");
sb.append(" in(");
for (String v:values) {
sb.append("'").append(v).append("'").append(",");
}
sb.deleteCharAt(sb.length()-1);
sb.append(")");
} else {
sb.append("=").append("'").append(fieldValue).append("'");
}
} else if (fieldValue instanceof Integer) {
sb.append("=").append(fieldValue);
}
}
return sb.toString();
}
}
可以看到丁溅,整個項目比較的簡單,通過調(diào)用query(Filter f)方法探遵,來實(shí)現(xiàn)打印SQL語句窟赏,下面我們詳細(xì)看看這個方法做了什么:
毋庸置疑,也是通過反射Filte類來實(shí)現(xiàn)邏輯的箱季,根據(jù)反射得到類里面的信息涯穷,包括類名上的注解和方法上的注解,然后獲取注解的值藏雏,最后經(jīng)過拼接字符串來實(shí)現(xiàn)SQL語句返回拷况!
最后附上源碼:
https://github.com/Gui-kai/BaseJava
覺得好,請點(diǎn)個贊掘殴!