上篇詳細(xì)研究了Java中的反射操作以及Class類相關(guān)內(nèi)容闻坚,但在Java開發(fā)過程中,除了反射兢孝,往往還有泛型窿凤、
注解等相關(guān)特性操作組合使用來實(shí)現(xiàn)一些高級(jí)技術(shù),如Spring中就大量使用了反射和注解跨蟹,實(shí)現(xiàn)了諸如Bean
容器管理機(jī)制等操作雳殊,SpringMvc框架中大量使用了注解,實(shí)現(xiàn)了servlet容器的簡易操作等窗轩,現(xiàn)在我們開始詳
細(xì)的學(xué)習(xí)Java中的注解機(jī)制
注解是什么
日常開發(fā)中經(jīng)常提到注解夯秃,那么注解是什么呢?在Java中痢艺,注解就是給程序添加一些信息仓洼,用字符@開頭,可以用來修飾后續(xù)的其他代碼元素堤舒,比如類色建、接口、字段植酥、方法、參數(shù)、構(gòu)造方法等友驮,往往注解還搭配編譯器漂羊、程序運(yùn)行時(shí)以及其他工具或者插件使用,用于實(shí)現(xiàn)代碼功能的增強(qiáng)或者修改程序的行為等操作
Java內(nèi)置注解
在Java中內(nèi)置了一些注解卸留,用來在類走越、方法申明使用,從而實(shí)現(xiàn)編譯器檢查耻瑟、避免編譯器檢查等操作旨指,同時(shí)也提高了java邏輯的嚴(yán)謹(jǐn)性,而在Java中內(nèi)置的常見注解莫過于@Override喳整、@Deprecated谆构、@SuppressWarnings三個(gè),下面分別介紹這三個(gè)常見的java內(nèi)置注解的作用
@Override
@Override注解修飾于方法上框都,表明當(dāng)前方法不是當(dāng)前類首先申明的搬素,而是由父類或者接口中繼承來的方法,并且當(dāng)前類進(jìn)行了方法重寫操作魏保,比如:
public class Base {
public void action() {};
}
現(xiàn)在有一個(gè)父類Base熬尺,申明一個(gè)方法--action,現(xiàn)在有一個(gè)子類繼承了Base類谓罗,并且重寫了action方法的內(nèi)部實(shí)現(xiàn)粱哼,如下:
public class Child extends Base {
@Override
public void action(){
System.out.println("child action");
}
}
可以看到在Child類的action方法上有一個(gè)@Override注解,代表著此方法是Child類重新實(shí)現(xiàn)并且繼承于Base類檩咱,但是當(dāng)我們把這個(gè)注解刪除揭措,發(fā)現(xiàn)工程無論是編譯還是運(yùn)行,和之前一般無二税手,那么編譯器為什么還要給我們默認(rèn)添加這個(gè)注解呢蜂筹?其實(shí)我們可以反過來思考,如果我們的類中有一個(gè)方法芦倒,并且使用了@Override注解進(jìn)行修飾艺挪,但是當(dāng)前類并沒有從別的類或者接口繼承來這個(gè)方法,或者此類本身就是獨(dú)立的類兵扬,這個(gè)時(shí)候會(huì)發(fā)生什么呢麻裳?
public class Child extends Base {
@Override
public void action(){
System.out.println("child action");
}
//action1方法在Base中不存在
@Override
public void action1(){
System.out.println("child class");
}
}
很明顯當(dāng)我們加上注解的瞬間,編譯器就會(huì)直接提示異常器钟,要求我們刪除當(dāng)前注解津坑。從這可以看出來,@Override注解有助于幫助編譯器檢查語法錯(cuò)誤傲霸,執(zhí)行更嚴(yán)格的代碼檢查疆瑰。
@Deprecated
@Deprecated注解可以修飾的范圍很廣眉反,不僅可以作用在方法上,還可以修飾類穆役、字段以及參數(shù)等所有注解可以修飾的范圍寸五,此注解代表被修飾的元素已經(jīng)過時(shí),并且警告使用者建議使用其他方法耿币,不再使用當(dāng)前方法梳杏。例如,Date類中就有很多方法被標(biāo)識(shí)已經(jīng)過時(shí)了:
@Deprecated
public Date(int year, int month, int date){
.....
}
@Deprecated
public int getYear(){
.....
}
當(dāng)我們?cè)谑褂眠@些方法的時(shí)候淹接,ide往往會(huì)給我們加上一個(gè)刪除線用來輔助提示調(diào)用者此方法已經(jīng)過時(shí)十性,隨時(shí)都可能在下個(gè)版本刪除,不建議使用塑悼。而在Java9中劲适,Deprecated注解又多了可填的屬性,分別是since和forRemoval拢肆,since屬性類型為String類型,表示被修飾的元素從哪一個(gè)版本號(hào)開始過時(shí)减响,不建議使用,而forRemoval屬性為Boolean類型郭怪,表示將來在過時(shí)的時(shí)候是否會(huì)刪除當(dāng)前元素支示,標(biāo)記為true代表將來會(huì)刪除此元素,使用者請(qǐng)慎用鄙才。而在Java9中颂鸿,Integer包裝類的構(gòu)造方法中就有使用此特性標(biāo)識(shí)的,如下:
@Deprecated(since="9")
public Integer(int value) {
this.value = value;
}
@SuppressWarnings
@SuppressWarnings注解則是表示壓制Java中的編譯警告措施攒庵,使得編譯器放棄被修飾元素的編譯警告嘴纺,此注解有一個(gè)必填參數(shù),表示壓制的是哪種類型的警告浓冒,此注解也可以使用在幾乎所有元素中栽渴。比如我們?cè)陂_發(fā)中使用了Date類中的一些過期或者有異常拋出風(fēng)險(xiǎn)的方法,ide往往就會(huì)添加一個(gè)警告的黃線稳懒,而我們?cè)诜椒ㄉ咸砑覢SuppressWarnings注解闲擦,則會(huì)發(fā)現(xiàn)警告消失不見,如下:
//添加在方法上场梆,取消掉編譯器的嚴(yán)格檢查警告機(jī)制
@SuppressWarnings({"deprecation","unused"})
public static void main(String[] args) {
Date date = new Date(2019, 10, 12);
int year = date.getYear();
}
常見的庫中的注解
日常開發(fā)使用的庫中也有著大量的注解墅冷,例如Jackson、SpringMvc等或油,下面就簡單介紹下常見庫中的常見注解使用
Jackson
Jackson是一個(gè)通用的序列化庫寞忿,程序員使用過程中可以使用它提供的注解機(jī)制對(duì)序列化進(jìn)行定制化操作,比如:
.使用@JsonIgnore和@JsonIgnoreProperties配置序列化的過程中忽略部分字段
.使用@JsonManagedReference和@JsonBackReference可以配置實(shí)例之間的互相引用
.使用@JsonProperty和@JsonFormat配置序列化的過程中字段名稱和屬性字段的格式等
Servlet3.0
隨著web開發(fā)技術(shù)的發(fā)展顶岸,Java web已經(jīng)發(fā)展到了Servlet3.0腔彰,在早期使用Servlet的時(shí)候叫编,我們只能在web.xml中配置,但是當(dāng)我們使用Servlet3.0的時(shí)候開始霹抛,已經(jīng)開始支持注解了宵溅,比如我們可以使用@WebServlet配置一個(gè)類為Servlet類,如下:
@WebServlet(urlPatterns = "/async", asyncSupported = true)
public class AsyncDemoServlet extends HttpServlet {
//..............
}
SpringMvc
同樣的上炎,在web開發(fā)中,我們往往還會(huì)使用SpringMvc框架來簡化開發(fā)雏搂,其框架的大量注解可以幫助我們減少大量的業(yè)務(wù)代碼藕施,例如一個(gè)請(qǐng)求的參數(shù)和字段/實(shí)例之間的映射關(guān)系,一個(gè)方法使用的是Http的什么請(qǐng)求方法凸郑,對(duì)應(yīng)請(qǐng)求的某個(gè)路徑裳食,同樣的請(qǐng)求如何解析,返回的響應(yīng)報(bào)文格式定義等芙沥,這些都可以使用注解來簡化實(shí)現(xiàn)诲祸,一個(gè)簡單的Mvc操作如下:
@Controller
@RequestMapping("/hello")
public class HelloController {
@GetMapping("/test")
@ResponseBody
public String test(){
return "hello test";
}
}
其中@Controller注解標(biāo)明當(dāng)前的類是SpringMvc接管的一個(gè)Bean實(shí)例,@RequestMapping("/hello")則是代表當(dāng)前Bean的前置請(qǐng)求路徑比如是/hello開頭而昨, @GetMapping("/test")則是表示test方法被訪問必須是Http請(qǐng)求的get請(qǐng)求救氯,并且路徑必須是/hello/test為路徑前置,@ResponseBody注解則是標(biāo)明了當(dāng)前請(qǐng)求的相應(yīng)信息按照默認(rèn)的格式返回(根據(jù)后綴名來確定格式)
注解的創(chuàng)建
從上面我們可以看到使用注解的確可以很方便的簡化我們開發(fā)過程歌憨,因此很多庫和開發(fā)過程中着憨,也會(huì)使用大量的注解簡化開發(fā),那么這些注解我們?nèi)绾螌?shí)現(xiàn)呢务嫡?首先我們先看看最常見的注解@Override的創(chuàng)建:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
從上面我們可以看到甲抖,Override類中使用了@Target元注解和@Retention元注解來定義整個(gè)注解,@Target表示需要注解的目標(biāo)心铃,@Retention則是標(biāo)明當(dāng)前注解的信息可以保留到Java的什么階段准谚,而除了這兩個(gè)元注解以外,還有兩個(gè)元注解@Documented和@Inherited去扣,@Documented用來表示當(dāng)前的注解信息是否包含到文檔中柱衔,@Inherited注解則是和注解之間的繼承有對(duì)應(yīng)的關(guān)系,那么這些元注解具體有什么作用厅篓,以及具體有哪些參數(shù)可以選擇呢秀存?接下來我們便分別學(xué)習(xí)一下
@Target
@Target注解表示當(dāng)前注解可以使用在什么類型的元素上,這里的值可以多選羽氮,即一個(gè)注解可以作用在多種不同類型的元素上净嘀,具體的可選值在ElementType枚舉類中,值如下:
取值 | 解釋 |
---|---|
TYPE | 表示作用在類纫雁、接口上 |
FIELD | 表示作用在字段,包括枚舉常量中 |
METHOD | 表示作用在方法中 |
PARAMETER | 表示作用在方法中的參數(shù)中 |
CONSTRUCTOR | 表示作用在構(gòu)造方法中 |
LOCAL_VARIABLE | 表示作用在本地常量中 |
MODULE | 表示作用在部分模塊中(Java9引入的概念) |
ANNOTATION_TYPE | 表示當(dāng)前注解作用在定義其他注解中祈纯,即元注解 |
PACKAGE | 表示當(dāng)前注解使用在包的申明中 |
TYPE_PARAMETER | 表明當(dāng)前注解使用在類型參數(shù)的申明中(Java8新增) |
TYPE_USE | 表明當(dāng)前注解使用在具體使用類型中(Java8新增) |
當(dāng)使用多個(gè)作用域范圍的時(shí)候,使用{}包裹多個(gè)參數(shù)叼耙,比如@SuppressWarnings注解的Target就有多個(gè)腕窥,在Java7中的定義為:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
即代表SuppressWarnings注解均可以作用在這七種元素上
@Retention
@Retention注解則是表明了當(dāng)前注解可以保留到Java多個(gè)階段的哪一個(gè)階段,參數(shù)類型為RetentionPolicy枚舉類筛婉,可取值如下:
取值 | 解釋 |
---|---|
SOURCE | 此注解僅在源代碼階段保留簇爆,編譯后即丟失注解部分 |
CLASS | 表示編譯后依然保留在Class字節(jié)碼中,但是加載時(shí)不一定會(huì)在內(nèi)存中 |
RUNTIME | 表示不僅保留在Class字節(jié)碼中爽撒,一直到內(nèi)存使用時(shí)仍然存在 |
此注解有默認(rèn)值入蛆,即當(dāng)我們沒有申明@Retention的時(shí)候,默認(rèn)則是Class取值范圍
@Documented
@Documented注解沒有具體的參數(shù)硕勿,使用此元注解哨毁,則表示帶有類型的注解將由javadoc記錄
@Inherited
@Inherited注解與注解的繼承有關(guān)系,具體關(guān)系為如果使用了當(dāng)前的元注解源武,則表示此注解可以被其他的注解的子類直接繼承扼褪,但是需要注意的是對(duì)已實(shí)現(xiàn)接口上的注解將沒有作用。
我們通過一個(gè)案例來了解@Inherited注解的作用粱栖,首先我們定義一個(gè)Test注解:
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
}
Test注解上有@Inherited元注解修飾话浇,則表明Test注解會(huì)被繼承,接著我們?cè)贐ase類上使用Test注解:
@Test
public class Base {
}
這個(gè)時(shí)候?qū)崿F(xiàn)一個(gè)子類Child闹究,我們來通過反射類Class中的isAnnotationPresent方法打印凳枝,看此類中的Test是否存在且是來自于父類繼承而來:
public class Child extends Base {
}
-----------------------------------
public static void main(String[] args) {
System.out.println(Child.class.isAnnotationPresent(Test.class));//true
}
最后輸出的結(jié)果為true,則表明Child類中存在@Test注解跋核,并且是由繼承父類而來
使用注解實(shí)現(xiàn)簡單定制序列化
上面我們已經(jīng)學(xué)習(xí)了注解定義和創(chuàng)建相關(guān)的內(nèi)容岖瑰,接下來我們利用注解簡單實(shí)現(xiàn)通用格式化輸出的類SimpleFormatter,類中有一個(gè)方法format砂代,方法參數(shù)為Object類型的實(shí)例對(duì)象蹋订,即表明了對(duì)象的序列化方式,類定義如下:
/**
* 通用格式轉(zhuǎn)換輸出類
*/
public class SimpleFormatter {
/**
* 通用格式化方法==>將obj對(duì)象輸出為String
* @param obj
* @return
*/
public static String format(Object obj){
try{
Class<?> cls = obj.getClass();
StringBuilder builder = new StringBuilder();
for (Field field : cls.getDeclaredFields()) {
if(!field.isAccessible()){
field.setAccessible(true);//放棄java安全檢測刻伊,設(shè)置可以訪問私有字段
}
//獲取Label注解-輸出的字段名稱
Label label = field.getAnnotation(Label.class);
String name = null == label ? field.getName() : label.value();
//獲取字段對(duì)應(yīng)的value
Object value = field.get(obj);
//如果是Date類型露戒,走時(shí)間格式化
if(null != value && field.getType() == Date.class){
value = formatter(field,value);
}
builder.append(name + "?" + value + "\n");
}
return builder.toString();
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException("格式化輸出失敗:"+e.getMessage());
}
}
/**
* 針對(duì)時(shí)間類型字段進(jìn)行格式化的方法
*/
private static Object formatter(Field field, Object value) {
Format format = field.getAnnotation(Format.class);
if(null == format){
return value;
}
String pattern = format.pattern();
String timezone = format.timezone();
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
sdf.setTimeZone(TimeZone.getTimeZone(timezone));
return sdf.format(value);
}
}
而除了格式化的類以外,我們還定義了兩個(gè)注解捶箱,一個(gè)用來表明格式化的時(shí)候的字段名稱智什,一個(gè)用來針對(duì)時(shí)間格式字段輸出的格式,如下:
/**
* Labl表明當(dāng)前字段輸出的名稱丁屎,僅作用在字段上
*/
@Retention(RUNTIME)
@Target(FIELD)
public @interface Label {
String value() default "";
}
--------------------------------
/**
* Format注解作用在字段上荠锭,針對(duì)時(shí)間字段類型的輸出格式
*/
@Retention(RUNTIME)
@Target(FIELD)
public @interface Format {
String pattern() default "yyyy-MM-dd HH:mm:ss";
String timezone() default "GMT+8";
}
除此之外,我們還需要一個(gè)實(shí)例類:
public class Student {
@Label("姓名")
private String name;
@Label("出生日期")
@Format(pattern="yyyy/MM/dd")
private Date born;
@Label("分?jǐn)?shù)")
private double score;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBorn() {
return born;
}
public void setBorn(Date born) {
this.born = born;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
}
定義完畢后晨川,我們可以這么來使用:
public static void main(String[] args) throws ParseException {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
Student zhangsan = new Student();
zhangsan.setName("張三");
zhangsan.setBorn(format.parse("1990-12-12"));
zhangsan.setScore(655);
System.out.println(SimpleFormatter.format(zhangsan));
}
輸出的結(jié)果為:
姓名?張三
出生日期?1990/12/12
分?jǐn)?shù)?655.0
至此证九,一個(gè)簡單的格式化輸出類就完成了