註解就這麽簡單
前言
今天要講的是註解,對於本章節,最好是有Servlet基礎的人查閱~因為單純是Java基礎的話,可能用不上註解這個東西。但如果開發過Servlet,就對@WebServlet
不會陌生。
現在的開發都推崇使用註解來進行開發,這樣就可以免去寫XML配置了,十分方便的一項技術~
學習註解可以更好地理解註解是怎麽工作的,看見註解了就可以想到它的運行原理了~。
如果有錯的地方請大家多多包涵並歡迎在評論區指正~
一、什麽是註解?
註解:Annotation....
註解其實就是代碼中的特殊標記,這些標記可以在編譯、類加載、運行時被讀取,並執行相對應的處理。
二、為什麽我們需要用到註解?
傳統的方式,我們是通過配置文件(xml文件)來告訴類是如何運行的
有了註解技術以後,我們就可以通過註解告訴類如何運行
例如:我們以前編寫Servlet的時候,需要在web.xml文件配置具體的信息
我們使用了註解以後,可以直接在Servlet源代碼上,增加註解...Servlet就被配置到Tomcat上了。也就是說,註解可以給類、方法上註入信息。
明顯地可以看出,這樣是非常直觀的,並且Servlet規範是推崇這種配置方式的。
三、基本Annotation
在java.lang包下存在著5個基本的Annotation,其中有3個Annotation我們是非常常見的了。
3.1@Overried
重寫註解
如果我們使用IDE重寫父類的方法,我們就可以看見它了。那它有什麽用呢??
@Overried
是告訴編譯器要檢查該方法是實現父類的...可以幫我們避免一些低級的錯誤...
比如,我們在實現equals()方法的時候,把euqals()打錯了,那麽編譯器就會發現該方法並不是實現父類的,與註解@Overried
沖突,於是就會給予錯誤。
3.2@Deprecated
過時註解
該註解也非常常見,Java在設計的時候,可能覺得某些方法設計得不好,為了兼容以前的程序,是不能直接把它拋棄的,於是就設置它為過時。
Date對象中的toLocalString()就被設置成過時了
@Deprecated
public String toLocaleString() {
DateFormat formatter = DateFormat.getDateTimeInstance ();
return formatter.format(this);
}
當我們在程序中調用它的時候,在IDE上會出現一條橫杠,說明該方法是過時的。
3.3@SuppressWarnings
抑制編譯器警告註解
該註解在我們寫程序的時候並不是很常見,我們可以用它來讓編譯器不給予我們警告
當我們在使用集合的時候,如果沒有指定泛型,那麽會提示安全檢查的警告
如果我們在類上添加了@SuppressWarnings
這個註解,那麽編譯器就不會給予我們警告了
3.4@SafeVarargs
Java 7“堆汙染”警告
什麽是堆汙染呢??當把一個不是泛型的集合賦值給一個帶泛型的集合的時候,這種情況就很容易發生堆汙染....
這個註解也是用來抑制編譯器警告的註解...用的地方並不多,我也不詳細說明了......有用到的時候再回來填坑吧。
3.5@FunctionalInterface
@FunctionalInterface
用來指定該接口是函數式接口
用該註解顯示指定該接口是一個函數式接口。
四、自定義註解基礎
上面講解的是java.lang包下的5個註解,我們是可以自己來寫註解,給方法或類註入信息。
4.1標記Annotation
沒有任何成員變量的註解稱作為標記註解,@Overried
就是一個標記註解
//有點像定義一個接口一樣,只不過它多了一個@
public @interface MyAnnotation {
}
4.2元數據Annotation
我們自定義的註解是可以帶成員變量的,定義帶成員變量的註解叫做元數據Annotation
在註解中定義成員變量,語法類似於聲明方法一樣....
public @interface MyAnnotation {
//定義了兩個成員變量
String username();
int age();
}
註意:在註解上定義的成員變量只能是String、數組、Class、枚舉類、註解
有的人可能會奇怪,為什麽註解上還要定義註解成員變量??聽起來就很復雜了....
上邊已經說了,註解的作用就是給類、方法註入信息。那麽我們經常使用XML文件,告訴程序怎麽運行。XML經常會有嵌套的情況
<書>
<作者>zhongfucheng</作者>
<價錢>22222</價錢>
</書>
那麽,當我們在使用註解的時候,也可能需要有嵌套的時候,所以就允許了註解上可以定義成員變量為註解。
4.3使用自定義註解
上面我們已經定義了一個註解了,下面我們來使用它吧
4.3.1常規使用
下面我有一個add的方法,需要username和age參數,我們通過註解來讓該方法擁有這兩個變量!
//註解擁有什麽屬性,在修飾的時候就要給出相對應的值
@MyAnnotation(username = "zhongfucheng", age = 20)
public void add(String username, int age) {
}
4.3.2默認值
當然啦,我們可以在註解聲明屬性的時候,給出默認值。那麽在修飾的時候,就可以不用具體指定了。
public @interface MyAnnotation {
//定義了兩個成員變量
String username() default "zicheng";
int age() default 23;
}
- 在修飾的時候就不需要給出具體的值了
@MyAnnotation()
public void add(String username, int age) {
}
4.3.3註解屬性為value
還有一種特殊的情況,如果註解上只有一個屬性,並且屬性的名稱為value,那麽在使用的時候,我們可以不寫value,直接賦值給它就行
public @interface MyAnnotation2 {
String value();
}
- 使用註解,可以不指定value,直接賦值
@MyAnnotation2("zhongfucheng")
public void find(String id) {
}
4.4把自定義註解的基本信息註入到方法上
上面我們已經使用到了註解,但是目前為止註解上的信息和方法上的信息是沒有任何關聯的。
我們使用Servlet註解的時候,僅僅調用註解,那麽註解的就生效了。這是Web容器把內部實現了。我們自己寫的自定義註解是需要我們自己來處理的。
那現在問題來了,我們怎麽把註解上的信息註入到方法上呢???我們利用的是反射技術
步驟可分為三部:
- 反射出該類的方法
- 通過方法得到註解上具體的信息
- 將註解上的信息註入到方法上
//反射出該類的方法
Class aClass = Demo2.class;
Method method = aClass.getMethod("add", String.class, int.class);
//通過該方法得到註解上的具體信息
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
String username = annotation.username();
int age = annotation.age();
//將註解上的信息註入到方法上
Object o = aClass.newInstance();
method.invoke(o, username, age);
當我們執行的時候,我們發現會出現異常...
此時,我們需要在自定義註解上加入這樣一句代碼(下面就會講到,為什麽要加入這句代碼)
@Retention(RetentionPolicy.RUNTIME)
再次執行的時候,我們就會發現,可以通過註解來把信息註入到方法中了。
五、JDK的元Annotation
前面我們已經介紹了java.lang包下的幾個基本Annotation了。在JDK中除了java.lang包下有Annotation,在java.lang.annotation下也有幾個常用的元Annotation。
在annotation包下的好幾個元Annotation都是用於修飾其他的Annotation定義。
5.1@Retention
上面在將註解信息註入到方法中的時候,我們最後加上了@Retention
的註解....不然就會報錯了..那它是幹什麽用的呢?
@Retention
只能用於修飾其他的Annotation,用於指定被修飾的Annotation被保留多長時間。
@Retention
包含了一個RetentionPolicy類型的value變量,所以在使用它的時候,必須要為value成員變量賦值
value變量的值只有三個:
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
java文件有三個時期:編譯,class,運行。@Retention默認是class
前面我們是使用反射來得到註解上的信息的,**因為@Retention默認是class,而反射是在運行時期來獲取信息的**。因此就獲取不到Annotation的信息了。於是,就得在自定義註解上修改它的RetentionPolicy值
5.2@Target
@Target
也是只能用於修飾另外的Annotation,它用於指定被修飾的Annotation用於修飾哪些程序單元
@Target
是只有一個value成員變量的,該成員變量的值是以下的:
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE
}
如果@Target
指定的是ElementType.ANNOTATION_TYPE,那麽該被修飾的Annotation只能修飾Annotaion
5.3@Documented
@Documented
用於指定被該Annotation修飾的Annotation類將被javadoc工具提取成文檔。
該元Annotation用得挺少的....
5.4@Inherited
@Inherited
也是用來修飾其他的Annotation的,被修飾過的Annotation將具有繼承性。。。
例子:
@xxx
是我自定義的註解,我現在使用@xxx
註解在Base類上使用....- 使用
@Inherited修飾@xxx註解
- 當有類繼承了Base類的時候,該實現類自動擁有
@xxx
註解
六、註入對象到方法或成員變量上
6.1把對象註入到方法上
前面我們已經可以使用註解將基本的信息註入到方法上了,現在我們要使用的是將對象註入到方法上.....
上邊已經說過了,註解上只能定義String、枚舉類、Double之類的成員變量,那怎麽把對象註入到方法上呢?
6.1.2模擬場景:
- Person類,定義username和age屬性,擁有uername和age的getter和setter方法
public class Person {
private String username;
private int age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
- PersonDao類,PersonDao類定義了Person對象,擁有person的setter和getter方法
public class PersonDao {
private Person person;
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
- 現在我要做的就是:使用註解將Person對象註入到setPerson()方法中,從而設置了PersonDao類的person屬性
public class PersonDao {
private Person person;
public Person getPerson() {
return person;
}
//將username為zhongfucheng,age為20的Person對象註入到setPerson方法中
@InjectPerson(username = "zhongfucheng",age = 20)
public void setPerson(Person person) {
this.person = person;
}
}
步驟:
①: 自定義一個註解,屬性是和JavaBean類一致的
//註入工具是通過反射來得到註解的信息的,於是保留域必須使用RunTime
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectPerson {
String username();
int age();
}
②:編寫註入工具
//1.使用內省【後邊需要得到屬性的寫方法】,得到想要註入的屬性
PropertyDescriptor descriptor = new PropertyDescriptor("person", PersonDao.class);
//2.得到要想註入屬性的具體對象
Person person = (Person) descriptor.getPropertyType().newInstance();
//3.得到該屬性的寫方法【setPerson()】
Method method = descriptor.getWriteMethod();
//4.得到寫方法的註解
Annotation annotation = method.getAnnotation(InjectPerson.class);
//5.得到註解上的信息【註解的成員變量就是用方法來定義的】
Method[] methods = annotation.getClass().getMethods();
//6.將註解上的信息填充到person對象上
for (Method m : methods) {
//得到註解上屬性的名字【age或name】
String name = m.getName();
//看看Person對象有沒有與之對應的方法【setAge(),setName()】
try {
//6.1這裏假設:有與之對應的寫方法,得到寫方法
PropertyDescriptor descriptor1 = new PropertyDescriptor(name, Person.class);
Method method1 = descriptor1.getWriteMethod();//setAge(), setName()
//得到註解中的值
Object o = m.invoke(annotation, null);
//調用Person對象的setter方法,將註解上的值設置進去
method1.invoke(person, o);
} catch (Exception e) {
//6.2 Person對象沒有與之對應的方法,會跳到catch來。我們要讓它繼續遍歷註解就好了
continue;
}
}
//當程序遍歷完之後,person對象已經填充完數據了
//7.將person對象賦給PersonDao【通過寫方法】
PersonDao personDao = new PersonDao();
method.invoke(personDao, person);
System.out.println(personDao.getPerson().getUsername());
System.out.println(personDao.getPerson().getAge());
③:總結一下步驟
其實我們是這樣把對象註入到方法中的:
- 得到想要類中註入的屬性
- 得到該屬性的對象
- 得到屬性對應的寫方法
- 通過寫方法得到註解
- 獲取註解詳細的信息
- 將註解的信息註入到對象上
- 調用屬性寫方法,將已填充數據的對象註入到方法中
6.2把對象註入到成員變量
上面已經說了如何將對象註入到方法上了,那麽註入到成員變量上也是非常簡單的。
步驟:
①:在成員變量上使用註解
public class PersonDao {
@InjectPerson(username = "zhongfucheng",age = 20) private Person person;
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
②:編寫註入工具
//1.得到想要註入的屬性
Field field = PersonDao.class.getDeclaredField("person");
//2.得到屬性的具體對象
Person person = (Person) field.getType().newInstance();
//3.得到屬性上的註解
Annotation annotation = field.getAnnotation(InjectPerson.class);
//4.得到註解的屬性【註解上的屬性使用方法來表示的】
Method[] methods = annotation.getClass().getMethods();
//5.將註入的屬性填充到person對象上
for (Method method : methods) {
//5.1得到註解屬性的名字
String name = method.getName();
//查看一下Person對象上有沒有與之對應的寫方法
try {
//如果有
PropertyDescriptor descriptor = new PropertyDescriptor(name, Person.class);
//得到Person對象上的寫方法
Method method1 = descriptor.getWriteMethod();
//得到註解上的值
Object o = method.invoke(annotation, null);
//填充person對象
method1.invoke(person, o);
} catch (IntrospectionException e) {
//如果沒有想對應的屬性,繼續循環
continue;
}
}
//循環完之後,person就已經填充好數據了
//6.把person對象設置到PersonDao中
PersonDao personDao = new PersonDao();
field.setAccessible(true);
field.set(personDao, person);
System.out.println(personDao.getPerson().getUsername());
七、總結
①:註入對象的步驟:得到想要註入的對象屬性,通過屬性得到註解的信息,通過屬性的寫方法將註解的信息註入到對象上,最後將對象賦給類。
②:註解其實就是兩個作用:
- 讓編譯器檢查代碼
- 將數據註入到方法、成員變量、類上
③:在JDK中註解分為了
- 基本Annotation
- 在lang包下,用於常用於標記該方法,抑制編譯器警告等
- 元Annotaion
- 在annotaion包下,常用於修飾其他的Annotation定義
如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以關註微信公眾號:Java3y
註解就這麽簡單