學習Java的注解有幾個好處:首先是能夠讀懂别人的代碼,特别是架構相關的代碼;其次是能夠讓程式設計更加簡潔、代碼更清晰;最後讓代碼看起來高大上(高逼格^^);
1.常見注解與分類
即使你對Java注解有多麼不熟悉,下面Java自帶的注解幾個你也一定遇到過:
- @Override:覆寫父類方法
- @Deprecated:表示該方法已過時,不建議使用,隻是為了前向相容
- @SuppressWarning:忽略警告
如果是經常用架構類的同學,那麼對第三方注解應該也是很熟悉的:
- 比如Spring: @Autowired、@Service、@Repository
- 比如Mybatis: @InsertProvider、@UpdateProvider、@Options
Java注解通常可以有不同次元分類,比如基于注解生效的時間進行分類,或者基于注解來源進行分類(Java自帶注解、第三方注解等)。如果基于注解生效的時間對注解進行分類,可以分為下面三類:
- 源碼注解:注解隻在源碼中存在,編譯成.class檔案後就不存在,比如@Override、@Deprecated等。
- 編譯時注解:注解在源碼和.class檔案中都存在,
- 運作時注解:通常采用反射機制來實作注解處理器,在程式運作階段起作用,能夠影響代碼運作邏輯的注解,比如:@Autowired、@Service等。
2.自定義注解
自定義Java注解時需要遵循一定的文法要求:
- 使用@interface關鍵字定義注解
- 成員以無參無異常的方式說明
注意:
- 可以用default為成員制定一個預設值
- 成員類型是受限的,合法的類型包括原始類型、String、Class等
- 如果注解隻有一個成員,則成員名必須取名為value(),在使用時可以忽略成員名和指派号(=)
- 注解類可以沒有成員,沒有成員的注解稱為辨別注解
自定義注解時涉及到的元注解(解釋注解的注解)
- @Target:聲明該注解的作用域,包括method、field、package、type(類或者接口)。
- @Retention:聲明注解的運作生命周期,包括runtime(運作時注解)、source(源碼注解)、class(編譯注解)
- @Inherited:聲明子類是否能繼承使用了該注解的父類的注解,該元注解隻對普通類有效,對于接口繼承無效。
- @Documented:生成javadoc時會包含注解的資訊
使用注解的文法:@<注解名>(<成員名1>=<成員值1>,<成員名2>=<成員值2>)
3.自定義運作時注解
運作時注解時通過反射擷取類、方法或成員上的運作時注解資訊,進而實作動态控制程式運作的邏輯。
自定義注解類:
package com.yongfxu.annotation;
import java.lang.annotation.*;
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
//@Retention(RetentionPolicy.SOURCE)
//@Retention(RetentionPolicy.CLASS)
@Inherited // 是否允許子類繼承使用了該注解的父類的注解,對接口繼承無效,對普通類繼承有效
@Documented // 是否生成javadoc
public @interface Description {
String desc();
String author();
int age() default 18;
}
定義一個接口,聲明要實作的方法:
package com.yongfxu.annotation;
public interface Person {
String name();
String sing();
int age();
}
聲明一個實作類,實作上述Person接口的方法,并增加注解:
package com.yongfxu.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
@Description(desc = "descClass", author = "authorClass")
public class Child implements Person {
@Override
@Description(desc = "descMethodName", author = "authorMethodName")
public String name() {
return "hahaha";
}
@Override
@Description(desc = "descMethodSing", author = "authorMethodSing")
public String sing() {
return null;
}
@Override
@Description(desc = "descMethodAge", author = "authorMethodAge")
public int age() {
return 0;
}
public static void main(String[] args) {
/*
* 使用類加載器的反射解析注解的方式,該注解必須是@Retention(RetentionPolicy.RUNTIME),
* 否則執行不出效果,因為類加載就是運作時執行的
*/
try {
// 1.使用類加載器加載類
Class aClass = Class.forName("com.yongfxu.annotation.Child");
// 2.找到類上的注解
boolean isExist = aClass.isAnnotationPresent(Description.class);
if (isExist) {
// 3.拿到注解執行個體
Description description = (Description) aClass.getAnnotation(Description.class);
System.out.println(description.author());
}
// 4.找到方法上的注解
Method[] methods = aClass.getMethods();
for (Method method : methods) {
boolean isMExist = method.isAnnotationPresent(Description.class);
if (isMExist) {
Description description = (Description) method.getAnnotation(Description.class);
System.out.println(description.author());
}
}
// 5.另外一種解析方法
for (Method method : methods) {
Annotation[] as = method.getAnnotations();
for (Annotation annotation : as) {
if (annotation instanceof Description) {
Description description = (Description) annotation;
System.out.println(description.author());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
執行上述類的main方法進行測試,得到下面的結果:
authorClass
authorMethodName
authorMethodSing
authorMethodAge
authorMethodName
authorMethodSing
authorMethodAge
4.Java注解的應用
下面根據所學的注解,實作一個持久層架構的注解,用于代替Hibernate,核心代碼就是通過注解來實作。下面直接上代碼。
聲明一個Table的注解類,用于标注資料庫的表名:
package com.yongfxu.mock;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Table {
String value();
}
聲明一個Column的注解類,用于标注待查詢的字段名:
package com.yongfxu.mock;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
String value();
}
聲明一個基礎Bean對象,用于承載資料庫擷取到的資料。為該類加上上述兩注解:
package com.yongfxu.mock;
@Table("user")
public class User {
@Column("user_name")
private String userName;
@Column("city")
private String city;
@Column("profession")
private String profession;
@Column("age")
private int age;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getProfession() {
return profession;
}
public void setProfession(String profession) {
this.profession = profession;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
最後,聲明一個Filter類,用于實作注解的解析流程:
package com.yongfxu.mock;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Filter {
public static void main(String[] args) {
// 根據使用者名和年齡生成sql查詢語句
User user1 = new User();
user1.setUserName("yongfxu");
user1.setAge(36);
// 根據使用者名和其中一個崗位生成sql查詢語句
User user2 = new User();
user2.setUserName("yongfxu");
user2.setProfession("programmer, manager, engineer");
System.out.println("sql1: " + query(user1));
System.out.println("sql2: " + query(user2));
}
private static String query(User user) {
// 1.擷取對象的class
Class userClass = user.getClass();
// 2.驗證注解是否存在
boolean isExist = userClass.isAnnotationPresent(Table.class);
if (!isExist) {
return null;
}
StringBuffer stringBuffer = new StringBuffer("select * from ");
// 3.擷取注解Table中的内容
Table table = (Table) userClass.getAnnotation(Table.class);
String tableName = table.value();
stringBuffer.append(tableName).append(" where 1 = 1");
Field[] fields = userClass.getDeclaredFields();
// 4.處理每個字段對應的SQL
for (Field field : fields) {
// 4.1 拿到字段的名字
boolean isFieldExist = field.isAnnotationPresent(Column.class);
if (!isFieldExist) {
continue;
}
Column column = field.getAnnotation(Column.class);
String columnName = column.value();
// 4.2 拿到字段的值
String fieldName = field.getName();
String methodName = new StringBuffer("get").append(fieldName.substring(0, 1).toUpperCase()).append(fieldName.substring(1)).toString();
Object fieldValue = null;
try {
Method method = userClass.getMethod(methodName);
fieldValue = method.invoke(user);
} catch (Exception e) {
e.printStackTrace();
}
if (fieldValue == null || (fieldValue instanceof Integer && (Integer) fieldValue == 0)) {
// 字段值為null或者int為0
continue;
}
// 4.3 拼接SQl
stringBuffer.append(" and ");
if (fieldValue instanceof Integer) {
stringBuffer.append(columnName).append(" = ").append(fieldValue);
} else if (fieldValue instanceof String) {
if (((String) fieldValue).contains(",")) {
String[] strArray = ((String) fieldValue).split(",");
stringBuffer.append(columnName).append(" in (");
for (String string : strArray) {
stringBuffer.append("'").append(string.trim()).append("',");
}
stringBuffer.deleteCharAt(stringBuffer.length() - 1);
stringBuffer.append(")");
} else {
stringBuffer.append(columnName).append(" = ").append("'").append(fieldValue).append("'");
}
}
}
return stringBuffer.toString();
}
}
至此,已經完成注解的實作。執行Filter類的main函數進行測試,得到下述結論:
sql1: select * from user where 1 = 1 and user_name = 'yongfxu' and age = 36
sql2: select * from user where 1 = 1 and user_name = 'yongfxu' and profession in ('programmer','manager','engineer')
程序已結束,退出代碼0