一、註解的基本概念
註解是JDK1.5及以後版本引入的,它可以用於創建文檔,跟蹤代碼中的依賴性,甚至執行基本編譯時檢查。註解是以‘@註解名’在代碼中存在的,它可以聲明在包、類、字段、方法、局部變量、方法參數等的前面,用來對這些元素進行說明,註釋。(摘自
百度百科)
按照運行機制,註解可以分爲三類:1、源碼註解:即註解只在源代碼中存在,在編譯成.class文件後就不存在了;2、編譯時註解:即在源碼和編譯的.class文件中都存在,例如常見的JDK 註解@Override、@Deprecated、@SuppressWarings;3、運行時註解:即註解在運行時仍然存在,甚至可能會影響程序的運行邏輯,例如Spring框架中用到的@Autowired註解。
我們也可以按照來源來將註解劃分爲三類:1、來自JDK的註解,就比如前面提到的@Override、@Deprecated、@SuppressWarings;2、來自第三方框架的註解,例如Spring的@Autowired;3、當然還有我們自己定義的註解。
還有一種特殊的註解,元註解,就是指註解的註解,這在自定義註解時經常使用。
二、自定義註解
我們可以通過自定義註解來實現我們自己想要的功能,下面通過一個簡單的例子來講解一下自定義註解的一些知識,下面的代碼是一個簡單的自定義註解。
package com.imooc.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Description {
String name();
int age() default 18;
}
首先我們可以看到,我們是使用@interface關鍵字定義註解的,其中註解裏定義的類似於一個方法的例如String name();其實是註解的成員,註解的成員通常以無參無異常的方式聲明的,我們也可以用default關鍵字爲成員指定一個默認值。這裏需要注意的是註解的成員的類型是受限的,只能是基本數據類型或者String,Class,Annotation,Enumeration這幾種類型,如果一個註解只有一個成員,那麼這個成員就必須取名爲value(),在使用註解時可以不顯式聲明成員名稱和等於號(=)。註解也可以沒有成員,沒有成員的註解稱爲標識註解。
除了註解定義的成員,我們發現在註解定義上面還有幾個註解,這些註解就是元註解,下面來簡單分析以下這幾個元註解:@Target表示定義的註解的作用域,通常作用域有constructor(構造方法聲明),field(字段聲明),local_variable(局部變量聲明),method(方法聲明),package(包聲明),parameter(參數聲明),type(類,接口聲明);@Retention表示定義的註解的運行週期,主要包括source:只在源碼顯示,編譯時會丟棄,class:編譯時會記錄到class中,運行時忽略,runtime:運行時存在,可以通過反射讀取;@Inherited表示被註解可以被子類繼承;@Documented表示生成JavaDoc文檔時會將註解寫入文檔。
當我們自定義了一個註解後就可以使用這個註解了,使用註解的語法是:@註解名(成員名=成員值,成員名=成員值……)
三、解析註解
在我們自定義了註解之後,可以通過反射獲取類、方法或成員上運行時的註解信息,從而實現動態控制程序運行時邏輯的目的。下面通過一個實例來解析上面的例子中定義的註解。我們先定義一個類,類名爲Boy,在這個類上和類的方法上使用註解,代碼如下。
package com.imooc.annotation;
@Description(name="A Class boy", age=20)
public class Boy {
@Override
@Description(name="A Method boy", age=20)
public String name() {
return null;
}
@Override
public int age() {
return 0;
}
}
然後我們再寫一個解析註解的類TestAnnotation,通過反射來獲取註解的信息。
package com.imooc.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
public class TestAnnotation {
public static void main(String[] args) {
//1、獲取Class信息
Class clazz = Boy.class;
//2、通過反射判斷類上是否存在註解
if(clazz.isAnnotationPresent(Description.class)) {
//3、獲取到類上的註解
Description desc = (Description) clazz.getAnnotation(Description.class);
//4、獲取類上註解的信息
System.out.println("name=" + desc.name() + ",age=" + desc.age());
//5、通過反射獲取類的方法
Method[] methods = clazz.getMethods();
for (Method method : methods) {
//6、判斷方法上是否有註解
if(method.isAnnotationPresent(Description.class)) {
//7、獲取方法上的註解
Description methodDesc = method.getAnnotation(Description.class);
//8、獲取方法上註解的信息
System.out.println("第一種方式獲得註解:name=" + methodDesc.name() + ",age=" + methodDesc.age());
}
//9、獲取方法上所有的註解信息
Annotation[] anns = method.getAnnotations();
for (Annotation annotation : anns) {
//10、遍歷註解獲取對應的註解信息
if(annotation instanceof Description) {
Description methodDesc = (Description) annotation;
System.out.println("第二種方式獲得註解:name=" + methodDesc.name() + ",age=" + methodDesc.age());
}
}
}
}
}
}
運行程序,程序執行如下結果,說明我們自定義的註解解析成功了。
name=A Class boy,age=20
第一種方式獲得註解:name=A Method boy,age=20
第二種方式獲得註解:name=A Method boy,age=20
接下來我們思考一下,定義一個接口Person,然後讓Boy實現這個接口,然後在Person上使用註解,代碼如下:
package com.imooc.annotation;
@Description(name="A Class boy", age=20)
public interface Person {
@Description(name="A Method boy", age=20)
public String name();
public int age();
}
package com.imooc.annotation;
public class Boy implements Person {
@Override
public String name() {
return null;
}
@Override
public int age() {
return 0;
}
}
這時再運行TestAnnotation,我們發現控制檯並沒有打印任何輸出,這說明@Inherited這個元註解對與接口的implements是不起作用的,那如果我們將接口Person改成抽象類,讓Boy繼承這個抽象類結果會是怎樣呢?
package com.imooc.annotation;
@Description(name="A Class boy", age=20)
public abstract class Person {
@Description(name="A Method boy", age=20)
public String name(){
return null;
}
public int age(){
return 0;
}
}
package com.imooc.annotation;
public class Boy extends Person {
@Override
public String name() {
return null;
}
@Override
public int age() {
return 0;
}
}
運行程序,控制檯打印如下輸出。
name=A Class boy,age=20
這說明@Inherited這個元註解對繼承extends是起作用的,但是隻會繼承類上的註解,並不會繼承該類方法上的註解。
四、一個簡單實例的記錄
這裏簡單記錄一個註解實現的實例,主要用於根據數據庫表和表中的字段進行組合,自動生成相應的SQL,下面是代碼。
package com.imooc.annotation.test;
@Table("user")
public class UserBean {
@Column("id")
private int id;
@Column("user_name")
private String userName;
@Column("nick_name")
private String nickName;
@Column("age")
private int age;
@Column("sex")
private String sex;
@Column("city")
private String city;
@Column("email")
private String email;
@Column("phone")
private String phone;
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 getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
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 getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
package com.imooc.annotation.test;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Table {
String value();
}
package com.imooc.annotation.test;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Column {
String value();
}
package com.imooc.annotation.test;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class SQLUtil {
public static void main(String[] args) throws Exception {
UserBean user = new UserBean();
user.setId(1);
user.setUserName("Jack");
user.setEmail("[email protected],[email protected],[email protected]");
System.out.println(generateSQL(user));
}
public static String generateSQL(Object obj) throws Exception {
StringBuffer sb = new StringBuffer();
Class clazz = obj.getClass();
if(clazz.isAnnotationPresent(Table.class)) {
Table table = (Table) clazz.getAnnotation(Table.class);
sb.append("select * form ").append(table.value()).append(" where 1=1 ");
Field[] fields = clazz.getDeclaredFields();
for(Field field : fields) {
if(field.isAnnotationPresent(Column.class)) {
Column column = field.getAnnotation(Column.class);
String columnName = column.value();
String getMethodName = "get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);
Method getMethod = clazz.getMethod(getMethodName);
Object returnValue = getMethod.invoke(obj);
if(returnValue instanceof String) {
if(returnValue != null) {
if(((String) returnValue).contains(",")) {
String[] spiltStrs = ((String) returnValue).split(",");
sb.append(" and " + columnName + " in(");
for (String spiltStr : spiltStrs) {
sb.append(" '" + spiltStr + "', ");
}
sb.append(") ");
} else {
sb.append(" and " + columnName + "='" + returnValue + "' ");
}
}
}
if(returnValue instanceof Integer) {
if((int)returnValue != 0) {
sb.append(" and " + columnName + "=" + returnValue + " ");
}
}
}
}
}
return sb.toString();
}
}