跟光磊學Java開發-Java註解
跟光磊學Java開發-Java註解
跟光磊學Java開發註解概述
註解(Annotation)相當於一種標記,在程式中加入註解就等於為程式打上某種標記以後,java編譯器、開發工具或者其他的框架就可以通過反射來獲取類以及類的成員上的註解,然後通過作相應的處理。
在方法上使用過的@Override
註解,編譯器在編譯時會檢查方法是不是重寫父類的方法。
在方法上使用的@Deprecated
註解表示方法已經過時,未來版本可能會刪除
在變數、方法、類上使用的@SuppressWarning(all)
忽略警告
在介面上定義的@FunctionInterface
註解,編譯器在編譯時會檢查該介面中是否只有一個抽象方法。
在方法上使用的@Test
後期再學習框架(MyBatis,Spring Framework,SpringBoot)時還會使用大量的註解配置來簡化程式。
自定義註解
在開發框架時會使用到自定義註解,註解和類、介面、列舉(Enum)是同級別關系。
自定義的註解格式如下
public @interface 註解名{
屬性
}
註解中可以包含0-N個屬性,屬性的定義個格式為:資料型別 屬性名();
,其中資料型別可以是如下幾種
- 八種基本型別:byte,short,int,long,float,double,char,boolean
- java.lang.String型別
- java.lang.Class型別
- 註解型別
- 列舉型別
- 以上5種資料型別的一維陣列型別
自定義註解@MyTest
,該註解沒有屬性
package net.ittimeline.java.core.jdk.feature.java5.annotation;
/**
* 自定義不含屬性的註解
*
* @author liuguanglei [email protected]
* @version 2021/1/1 4:19 下午
* @since JDK11
*/
public @interface MyTest {
}
註解定義完成之後就可以使用註解
/**
* 在方法上使用沒有屬性的自定義註解@MyTest
*/
@MyTest
public void testAnnotation() {
}
自定義包含多個屬性的註解@Table
package net.ittimeline.java.core.jdk.feature.java5.annotation;
/**
* 自定義帶兩個屬性的註解
*
* @author liuguanglei [email protected]
* @version 2021/1/1 4:23 下午
* @since JDK11
*/
public @interface Table {
/**
* 表名
*
* @return
*/
String name() default " ";
/**
* 表名字首
*
* @return
*/
String prefix() default "tbl_";
}
然後可以在類上使用帶屬性的註解,給註解的屬性賦值時,如果有多個屬性,需要使用逗號分割。
/**
* 在類上使用自定義註解@Table
* 多個屬性賦值使用,分割
*/
@Table(name = "tbl_user_info", prefix = "tbl_")
class UserInfo {
}
在使用註解時,有一些注意事項
- 如果註解有了屬性,必須賦值,註解的屬性可以有預設值,例如
String prefix() default "tbl_";
表示prefix屬性的預設值為tbl_,如果不想使用預設值,在使用註解時再給屬性賦值。 - 如果屬性的型別是一維陣列,當陣列的值只有一個的時候可以省略
{}
- 如果註解中只有一個屬性,並且屬性名是value,那麼給屬性賦值時屬性名value可以省略
元註解
JDK提供了@Target、@Retention兩個元註解, 元註解表示定在註解上的註解。
@Target
- @Target元註解用於限制註解作用在指定的位置上,預設註解可以在任意位置使用。
@Target註解的value()屬性取值型別是ElementType[]陣列,常用的位置有- ElementType.METHOD 表示註解作用在方法上
- ElementType.TYPE 表示註解作用在類上
- ElementType.FIELD 表示註解作用在屬性上
- ElementType.CONSTRUCTOR 表示註解作用在構造器上
package net.ittimeline.java.core.jdk.feature.java5.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
/**
* 自定義不含屬性的註解
*
* @author liuguanglei [email protected]
* @version 2021/1/1 4:19 下午
* @since JDK11
*/
@Target(ElementType.METHOD) //自定義MyTest註解只能作用在方法上
public @interface MyTest {
}
@Retention
- @Rentention :定義該註解保留在指定的階段,取值為RetentionPolicy型別,常用的取值有
- RetentionPolicy.SOURCE 註解保留在原始碼階段
- RetentionPolicy.CLASS 註解保留在編譯階段
- RetentionPolicy.RUNTIME 註解保留在執行階段
Java程式的執行流程是從原始碼由javac編譯生成位元組碼,然後由JVM解釋執行,因此如果@Retentinon註解的屬性值是RetentionPolicy.RUNTIME ,意味著原始碼、編譯和執行都會保留註解。
自定義註解@MyTest,保留到執行階段,只能作用在方法上
package net.ittimeline.java.core.jdk.feature.java5.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定義不含屬性的註解
*
* @author liuguanglei [email protected]
* @version 2021/1/1 4:19 下午
* @since JDK11
*/
@Target(ElementType.METHOD) //自定義MyTest註解只能作用在方法上
@Retention(RetentionPolicy.RUNTIME) //自定義MyTest註解作用在執行階段
public @interface MyTest {
}
自定義註解@Table,只能作用在類上,保留到執行階段
package net.ittimeline.java.core.jdk.feature.java5.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定義帶兩個屬性的註解
*
* @author liuguanglei [email protected]
* @version 2021/1/1 4:23 下午
* @since JDK11
*/
@Target(ElementType.TYPE) //表示@Table只能在類上使用
@Retention(RetentionPolicy.RUNTIME) // @Table註解儲存到執行階段
public @interface Table {
/**
* 表名
*
* @return
*/
String name() default " ";
/**
* 表名字首
*
* @return
*/
String prefix() default "tbl_";
}
註解解析
系統自帶的註解由系統解析,而自定義註解@Table和@MyTest預設只有標記,並沒有其他的功能,如果想要註解提供額外的功能,就需要手動實現。
反射的API們Class,Field,Constructor都實現了java.lang.reflect.AnnotatedElement介面
通過該介面提供的public <A extends Annotation> A getAnnotation(Class<A> annotationClass)
方法就可以獲取指定的註解物件以及該物件的屬性值
/**
* 自定義註解的使用,獲取UserInfo類的@Table註解,並且獲取該註解的屬性值
*
* @see Class#getAnnotation(Class) 獲取指定註解Class物件的註解物件,返回指定的註解物件
* @see Table#name()
* @see Table#prefix()
*/
@Test
public void testUserInfoTableAnnotation() {
//獲取UserInfo的class物件
Class<UserInfo> userInfoClass = UserInfo.class;
//獲取UserInfo的@Table註解,並返回Table註解物件
Table table = userInfoClass.getAnnotation(Table.class);
//根據註解物件獲取屬性值
log.info("獲取UserInfo類上@Tabele註解的name屬性值是{}", table.name());
log.info("獲取UserInfo類上@Tabele註解的prefix屬性值是{}", table.prefix());
}
程式執行結果
不僅如此,我們還可以使用@MyTest註解實現TestNG的@TestNG的功能
首先準備一個類,該類中包含了9個方法,其中有8個方法使用了@MyTest註解,一個方法沒有使用。
@Log4j2
class OrderHandler {
@MyTest
public void testRegister() {
log.info("註冊成功");
}
@MyTest
public void testLogin() {
log.info("登入成功");
}
@MyTest
public void testBrowser() {
log.info("瀏覽商品");
}
@MyTest
public void testAddShopCart() {
log.info("新增購物車成功");
}
@MyTest
public void pay() {
log.info("支付成功");
}
@MyTest
public void order() {
log.info("下單成功");
}
@MyTest
public void doStatisticsIP(UserInfo userInfo) {
log.info("統計使用者登入IP資訊");
}
public void doStatisticsLoginCount(UserInfo userInfo) {
log.info("統計使用者登入次數資訊");
}
@MyTest
public String getCurrentDefaultUser() {
return "admin";
}
}
在TestNG框架中,要求可以執行的單元測試方法必須沒引數,沒返回值,訪問許可權必須是public,否則無法執行單元測試
然後編寫一個處理該註解的handleMyTestAnnotation方法,只需要傳一個Class物件即可。
/**
* 處理@MyTest註解,即會反射呼叫包含@MyTest註解的方法
*
* @param clazz
* @param <T>
*/
public <T> void handleMyTestAnnotation(Class<T> clazz) {
//首先獲取Class物件的所有公共許可權方法
Method[] methods = clazz.getMethods();
try {
// 然後建立Class的物件
T type = clazz.newInstance();
//然後遍歷每個方法
for (Method method : methods) {
//獲取方法的名稱
String methodName = method.getName();
//如果方法上使用了@MyTest註解
if (method.isAnnotationPresent(MyTest.class)) {
//獲取方法的返回值
Class<Void> returnType = (Class<Void>) method.getReturnType();
//方法的返回值必須是void
if (returnType.getName().equals("void")) {
//獲取方法的引數
Class<?>[] parameterTypes = method.getParameterTypes();
//方法必須無引數
if (parameterTypes != null && parameterTypes.length == 0) {
//執行方法
method.invoke(type);
} else {
try {
String message = "方法" + methodName + "方法必須沒有引數";
throw new IllegalArgumentException(message);
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
}
}
} else {
try {
String message = "方法" + methodName + "的返回值必須是void";
throw new IllegalArgumentException(message);
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
}
}
} else {
log.info("方法{}沒有@MyTest註解,不執行", methodName);
}
}
} catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
然後在測試方法中testHandleMyTestAnnotation()中呼叫handleMyTestAnnotation()方法傳入OrderHandler的Class物件即可
/**
* 測試解析@MyTest註解
*
* @see CustomAnnotationTest#handleMyTestAnnotation(Class)
*/
@Test
public void testHandleMyTestAnnotation() {
handleMyTestAnnotation(OrderHandler.class);
}
程式執行結果
程式執行結果