spring-boot注解详解(六)
@Target
@Target說(shuō)明了Annotation所修飾的對(duì)象范圍:Annotation可被用于 packages、types(類(lèi)、接口、枚舉、Annotation類(lèi)型)、類(lèi)型成員(方法、構(gòu)造方法、成員變量、枚舉值)、方法參數(shù)和本地變量(如循環(huán)變量、catch參數(shù))。在A(yíng)nnotation類(lèi)型的聲明中使用了target可更加明晰其修飾的目標(biāo)。
作用:用于描述注解的使用范圍(即:被描述的注解可以用在什么地方)
取值(ElementType)有
ElementType.TYPE_PARAMETER(Type parameter declaration) 用來(lái)標(biāo)注類(lèi)型參數(shù), 栗子如下:
@Target(ElementType.TYPE_PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface TypeParameterAnnotation {}// 如下是該注解的使用例子 public class TypeParameterClass<@TypeParameterAnnotation T> {public <@TypeParameterAnnotation U> T foo(T t) {return null;} }ElementType.TYPE_USE(Use of a type) 能標(biāo)注任何類(lèi)型名稱(chēng),包括上面這個(gè)(ElementType.TYPE_PARAMETER的),栗子如下:
public class TestTypeUse {@Target(ElementType.TYPE_USE)@Retention(RetentionPolicy.RUNTIME)public @interface TypeUseAnnotation {}public static @TypeUseAnnotation class TypeUseClass<@TypeUseAnnotation T> extends @TypeUseAnnotation Object {public void foo(@TypeUseAnnotation T t) throws @TypeUseAnnotation Exception {}}// 如下注解的使用都是合法的@SuppressWarnings({ "rawtypes", "unused", "resource" })public static void main(String[] args) throws Exception {TypeUseClass<@TypeUseAnnotation String> typeUseClass = new @TypeUseAnnotation TypeUseClass<>();typeUseClass.foo("");List<@TypeUseAnnotation Comparable> list1 = new ArrayList<>();List<? extends Comparable> list2 = new ArrayList<@TypeUseAnnotation Comparable>();@TypeUseAnnotation String text = (@TypeUseAnnotation String)new Object();java.util. @TypeUseAnnotation Scanner console = new java.util.@TypeUseAnnotation Scanner(System.in);} }@Retention
元注解的作用就是負(fù)責(zé)注解其他注解。Java5.0定義了4個(gè)標(biāo)準(zhǔn)的meta-annotation類(lèi)型,它們被用來(lái)提供對(duì)其它 annotation類(lèi)型作說(shuō)明。Java5.0定義的元注解:
1.@Target,
2.@Retention,
3.@Documented,
4.@Inherited
這些類(lèi)型和它們所支持的類(lèi)在java.lang.annotation包中可以找到。下面我們看一下每個(gè)元注解的作用和相應(yīng)分參數(shù)的使用說(shuō)明。
@Target:
@Target說(shuō)明了Annotation所修飾的對(duì)象范圍:Annotation可被用于 packages、types(類(lèi)、接口、枚舉、Annotation類(lèi)型)、類(lèi)型成員(方法、構(gòu)造方法、成員變量、枚舉值)、方法參數(shù)和本地變量(如循環(huán)變量、catch參數(shù))。在A(yíng)nnotation類(lèi)型的聲明中使用了target可更加明晰其修飾的目標(biāo)。
作用:用于描述注解的使用范圍(即:被描述的注解可以用在什么地方)
取值(ElementType)有:
1.CONSTRUCTOR:用于描述構(gòu)造器
2.FIELD:用于描述域
3.LOCAL_VARIABLE:用于描述局部變量
4.METHOD:用于描述方法
5.PACKAGE:用于描述包
6.PARAMETER:用于描述參數(shù)
7.TYPE:用于描述類(lèi)、接口(包括注解類(lèi)型) 或enum聲明
使用實(shí)例:
@Target(ElementType.TYPE)
public @interface Table {
/**
* 數(shù)據(jù)表名稱(chēng)注解,默認(rèn)值為類(lèi)名稱(chēng)
* @return
*/
public String tableName() default “className”;
}
@Target(ElementType.FIELD)
public @interface NoDBColumn {
}
注解Table 可以用于注解類(lèi)、接口(包括注解類(lèi)型) 或enum聲明,而注解NoDBColumn僅可用于注解類(lèi)的成員變量。
@Retention:
@Retention定義了該Annotation被保留的時(shí)間長(zhǎng)短:某些Annotation僅出現(xiàn)在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會(huì)被虛擬機(jī)忽略,而另一些在class被裝載時(shí)將被讀取(請(qǐng)注意并不影響class的執(zhí)行,因?yàn)锳nnotation與class在使用上是被分離的)。使用這個(gè)meta-Annotation可以對(duì) Annotation的“生命周期”限制。
作用:表示需要在什么級(jí)別保存該注釋信息,用于描述注解的生命周期(即:被描述的注解在什么范圍內(nèi)有效)
取值(RetentionPoicy)有:
1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留)
3.RUNTIME:在運(yùn)行時(shí)有效(即運(yùn)行時(shí)保留)
Retention meta-annotation類(lèi)型有唯一的value作為成員,它的取值來(lái)自java.lang.annotation.RetentionPolicy的枚舉類(lèi)型值。具體實(shí)例如下:
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Column {public String name() default "fieldName";public String setFuncName() default "setField";public String getFuncName() default "getField"; public boolean defaultDBValue() default false; }Column注解的的RetentionPolicy的屬性值是RUTIME,這樣注解處理器可以通過(guò)反射,獲取到該注解的屬性值,從而去做一些運(yùn)行時(shí)的邏輯處理
@Documented:
@Documented用于描述其它類(lèi)型的annotation應(yīng)該被作為被標(biāo)注的程序成員的公共API,因此可以被例如javadoc此類(lèi)的工具文檔化。Documented是一個(gè)標(biāo)記注解,沒(méi)有成員。
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Column {public String name() default "fieldName";public String setFuncName() default "setField";public String getFuncName() default "getField"; public boolean defaultDBValue() default false; }@Inherited:
@Inherited 元注解是一個(gè)標(biāo)記注解,@Inherited闡述了某個(gè)被標(biāo)注的類(lèi)型是被繼承的。如果一個(gè)使用了@Inherited修飾的annotation類(lèi)型被用于一個(gè)class,則這個(gè)annotation將被用于該class的子類(lèi)。
注意:@Inherited annotation類(lèi)型是被標(biāo)注過(guò)的class的子類(lèi)所繼承。類(lèi)并不從它所實(shí)現(xiàn)的接口繼承annotation,方法并不從它所重載的方法繼承annotation。
當(dāng)@Inherited annotation類(lèi)型標(biāo)注的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強(qiáng)了這種繼承性。如果我們使用java.lang.reflect去查詢(xún)一個(gè)@Inherited annotation類(lèi)型的annotation時(shí),反射代碼檢查將展開(kāi)工作:檢查class和其父類(lèi),直到發(fā)現(xiàn)指定的annotation類(lèi)型被發(fā)現(xiàn),或者到達(dá)類(lèi)繼承結(jié)構(gòu)的頂層。
實(shí)例代碼:
/*** * @author peida**/ @Inherited public @interface Greeting {public enum FontColor{ BULE,RED,GREEN};String name();FontColor fontColor() default FontColor.GREEN; }自定義注解:
使用@interface自定義注解時(shí),自動(dòng)繼承了java.lang.annotation.Annotation接口,由編譯程序自動(dòng)完成其他細(xì)節(jié)。在定義注解時(shí),不能繼承其他的注解或接口。@interface用來(lái)聲明一個(gè)注解,其中的每一個(gè)方法實(shí)際上是聲明了一個(gè)配置參數(shù)。方法的名稱(chēng)就是參數(shù)的名稱(chēng),返回值類(lèi)型就是參數(shù)的類(lèi)型(返回值類(lèi)型只能是基本類(lèi)型、Class、String、enum)。可以通過(guò)default來(lái)聲明參數(shù)的默認(rèn)值。
定義注解格式:
public @interface 注解名 {定義體}
注解參數(shù)的可支持?jǐn)?shù)據(jù)類(lèi)型:
1.所有基本數(shù)據(jù)類(lèi)型(int,float,boolean,byte,double,char,long,short)
2.String類(lèi)型
3.Class類(lèi)型
4.enum類(lèi)型
5.Annotation類(lèi)型
6.以上所有類(lèi)型的數(shù)組
Annotation類(lèi)型里面的參數(shù)該怎么設(shè)定:
第一,只能用public或默認(rèn)(default)這兩個(gè)訪(fǎng)問(wèn)權(quán)修飾.例如,String value();這里把方法設(shè)為defaul默認(rèn)類(lèi)型;
第二,參數(shù)成員只能用基本類(lèi)型byte,short,char,int,long,float,double,boolean八種基本數(shù)據(jù)類(lèi)型和 String,Enum,Class,annotations等數(shù)據(jù)類(lèi)型,以及這一些類(lèi)型的數(shù)組.例如,String value();這里的參數(shù)成員就為String;
第三,如果只有一個(gè)參數(shù)成員,最好把參數(shù)名稱(chēng)設(shè)為"value",后加小括號(hào).例:下面的例子FruitName注解就只有一個(gè)參數(shù)成員。
簡(jiǎn)單的自定義注解和使用注解實(shí)例:
package annotation;import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/*** 水果名稱(chēng)注解* @author peida**/ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitName {String value() default ""; } package annotation;import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/*** 水果顏色注解* @author peida**/ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitColor {/*** 顏色枚舉* @author peida**/public enum Color{ BULE,RED,GREEN};/*** 顏色屬性* @return*/Color fruitColor() default Color.GREEN;} package annotation;import annotation.FruitColor.Color;public class Apple {@FruitName("Apple")private String appleName;@FruitColor(fruitColor=Color.RED)private String appleColor;public void setAppleColor(String appleColor) {this.appleColor = appleColor;}public String getAppleColor() {return appleColor;}public void setAppleName(String appleName) {this.appleName = appleName;}public String getAppleName() {return appleName;}public void displayName(){System.out.println("水果的名字是:蘋(píng)果");} }注解元素的默認(rèn)值:
注解元素必須有確定的值,要么在定義注解的默認(rèn)值中指定,要么在使用注解時(shí)指定,非基本類(lèi)型的注解元素的值不可為null。因此, 使用空字符串或0作為默認(rèn)值是一種常用的做法。這個(gè)約束使得處理器很難表現(xiàn)一個(gè)元素的存在或缺失的狀態(tài),因?yàn)槊總€(gè)注解的聲明中,所有元素都存在,并且都具有相應(yīng)的值,為了繞開(kāi)這個(gè)約束,我們只能定義一些特殊的值,例如空字符串或者負(fù)數(shù),一次表示某個(gè)元素不存在,在定義注解時(shí),這已經(jīng)成為一個(gè)習(xí)慣用法。例如:
package annotation;import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/*** 水果供應(yīng)者注解* @author peida**/ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitProvider {/*** 供應(yīng)商編號(hào)* @return*/public int id() default -1;/*** 供應(yīng)商名稱(chēng)* @return*/public String name() default "";/*** 供應(yīng)商地址* @return*/public String address() default ""; }定義了注解,并在需要的時(shí)候給相關(guān)類(lèi),類(lèi)屬性加上注解信息,如果沒(méi)有響應(yīng)的注解信息處理流程,注解可以說(shuō)是沒(méi)有實(shí)用價(jià)值。如何讓注解真真的發(fā)揮作用,主要就在于注解處理方法,下一步我們將學(xué)習(xí)注解信息的獲取和處理!
二、注解的使用:
第一步:新建一個(gè)annotation,名字為:MyAnnotation.java。
package com.dragon.test.annotation;import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/*** Created by gmq on 2015/9/10.*/ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation {String hello () default "hello";String world(); }第二步:建立一個(gè)MyTest.java 來(lái)使用上面的annotation。
package com.dragon.test.annotation;/*** Created by gmq on 2015/9/10.*/ public class MyTest {@MyAnnotation(hello = "Hello,Beijing",world = "Hello,world")public void output() {System.out.println("method output is running ");} }第三步:用反射機(jī)制來(lái)調(diào)用注解中的內(nèi)容
package com.dragon.test.annotation;import java.lang.annotation.Annotation; import java.lang.reflect.Method;/*** 用反射機(jī)制來(lái)調(diào)用注解中的內(nèi)容* Created by gmq on 2015/9/10.*/ public class MyReflection {public static void main(String[] args) throws Exception{// 獲得要調(diào)用的類(lèi)Class<MyTest> myTestClass = MyTest.class;// 獲得要調(diào)用的方法,output是要調(diào)用的方法名字,new Class[]{}為所需要的參數(shù)。空則不是這種Method method = myTestClass.getMethod("output", new Class[]{});// 是否有類(lèi)型為MyAnnotation的注解if (method.isAnnotationPresent(MyAnnotation.class)){// 獲得注解MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);// 調(diào)用注解的內(nèi)容System.out.println(annotation.hello());System.out.println(annotation.world());}System.out.println("----------------------------------");// 獲得所有注解。必須是runtime類(lèi)型的Annotation[] annotations = method.getAnnotations();for (Annotation annotation : annotations){// 遍歷所有注解的名字System.out.println(annotation.annotationType().getName());}} }輸出:
Hello,Beijing
Hello,world
@Documented
Documented注解表明這個(gè)注釋是由 javadoc記錄的,在默認(rèn)情況下也有類(lèi)似的記錄工具。 如果一個(gè)類(lèi)型聲明被注釋了文檔化,它的注釋成為公共API的一部分。
@Component
@Component and @Bean do two quite different things, and shouldn’t be confused.
@Component (and @Service and @Repository) are used to auto-detect and auto-configure beans using classpath scanning. There’s an implicit one-to-one mapping between the annotated class and the bean (i.e. one bean per class). Control of wiring is quite limited with this approach, since it’s purely declarative.
@Bean is used to explicitly declare a single bean, rather than letting Spring do it automatically as above. It decouples the declaration of the bean from the class definition, and lets you create and configure beans exactly how you choose.
看了一些文章,這兩個(gè)注解可以互換使用,但還有一些使用目的進(jìn)行區(qū)別的。
@Component被用在要被自動(dòng)掃描和裝配的類(lèi)上。
@Bean主要被用在方法上,來(lái)顯式聲明要用生成的類(lèi)。
現(xiàn)在項(xiàng)目上,本工程中的類(lèi),一般都使用@Component來(lái)生成bean。在把通過(guò)web service取得的類(lèi),生成Bean時(shí),使用@Bean和getter方法來(lái)生成bean。
@Conditional
Conditional 是由 SpringFramework 提供的一個(gè)注解,位于 org.springframework.context.annotation 包內(nèi),定義如下。
package org.springframework.context.annotation;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, ElementType.METHOD}) public @interface Conditional {Class<? extends Condition>[] value();}Conditional 注解類(lèi)里只有一個(gè) value 屬性,需傳入一個(gè) Condition 類(lèi)型的數(shù)組,我們先來(lái)看看這個(gè) Condition 接口長(zhǎng)什么樣。
package org.springframework.context.annotation;import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.core.type.AnnotatedTypeMetadata; public interface Condition {boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);}其中,matches() 方法傳入的參數(shù) ConditionContext 是專(zhuān)門(mén)為 Condition 而設(shè)計(jì)的一個(gè)接口類(lèi),可以從中獲取到Spring容器的以下對(duì)象信息。
當(dāng)一個(gè) Bean 被 Conditional 注解修飾時(shí),Spring容器會(huì)對(duì)數(shù)組中所有 Condition 接口的 matches() 方法進(jìn)行判斷,只有當(dāng)其中所有 Condition 接口的 matches()方法都為 ture 時(shí),才會(huì)創(chuàng)建 Bean 。
自定義Conditional
接下來(lái),我們將以一個(gè)國(guó)際化 I18n Bean 動(dòng)態(tài)創(chuàng)建為例(根據(jù)配置中的 i18n.lang 屬性值來(lái)動(dòng)態(tài)地創(chuàng)建國(guó)際化 I18n Bean),對(duì)如何使用 Conditional 注解進(jìn)行簡(jiǎn)單舉例:
當(dāng) i18n.lang=zh_CN 就創(chuàng)建中文 I18nChs Bean,
當(dāng) i18n.lang=en_US 就創(chuàng)建英文 I18nEng Bean。
創(chuàng)建好的兩個(gè) Condition 實(shí)現(xiàn)類(lèi) I18nChsCondition 和 I18nEngCondition 代碼如下。
I18n 接口定義如下。
public interface I18n {// 獲取 name 屬性的值String i18n(String name);}I18n 接口的兩個(gè)實(shí)現(xiàn)類(lèi) I18nChs 和 I18nEng 定義如下。
@Component @Conditional(I18nChsCondition.class) public class I18nChsImpl implements I18n {Map<String, String> map = new HashMap<String, String>() {private static final long serialVersionUID = 1L;{put("lang", "中文");}};@Overridepublic String i18n(String name) {return map.get(name);} } @Component @Conditional(I18nEngCondition.class) public class I18nEngImpl implements I18n {Map<String, String> map = new HashMap<String, String>() {private static final long serialVersionUID = 1L;{put("lang", "English");}};@Overridepublic String i18n(String name) {return map.get(name);}}在啟動(dòng)類(lèi)中添加測(cè)試代碼代碼如下。
@SpringBootApplication public class App {public static void main( String[] args ){ConfigurableApplicationContext context = SpringApplication.run(App.class, args);I18n i18n = context.getBean(I18n.class);System.out.println(i18n.getClass().getName());System.out.println(i18n.i18n("lang"));context.close();} }配置 application.properties 內(nèi)容如下:
# language : zh_CN/Chinese,en_US/America i18n.lang=zh_CN運(yùn)行程序,打印結(jié)果:
com.pengjunlee.condition.I18nChsImpl 中文配置 application.properties 內(nèi)容如下:
# language : zh_CN/Chinese,en_US/America i18n.lang=en_US再次運(yùn)行程序,打印結(jié)果:
com.pengjunlee.condition.I18nEngImpl English為了書(shū)寫(xiě)和調(diào)用方便,我們還可以把上面的條件定義成注解,以 I18nChsCondition 為例,定義代碼如下。
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(I18nChsCondition.class) public @interface I18nChs {}將 I18nChs 注解添加到 I18nChsImpl 上。
@Component @I18nEng public class I18nChsImpl implements I18n {//內(nèi)容同上,此處省略}SpringBoot 擴(kuò)展注解
從上面的示例不難看出,如果要使用我們自定義條件類(lèi)實(shí)現(xiàn)起來(lái)還是有點(diǎn)小麻煩的,不過(guò)比較慶幸的是, SpringBoot 在 Conditional 注解的基礎(chǔ)上已經(jīng)提前為我們定義好了一系列功能豐富的注解,我們可以直接使用。
接下來(lái)我們使用 ConditionalOnProperty 注解來(lái)實(shí)現(xiàn)上面的國(guó)際化示例。
僅需修改 I18nChsImpl 和 I18nEngImpl 兩個(gè)實(shí)現(xiàn)組件類(lèi),其他代碼不變,程序執(zhí)行結(jié)果與之前相同。
@Component @ConditionalOnProperty(name = "i18n.lang", havingValue = "zh_CN", matchIfMissing = true) public class I18nChsImpl implements I18n {//內(nèi)容同上,此處省略} @Component @ConditionalOnProperty(name = "i18n.lang", havingValue = "en_US", matchIfMissing = false) public class I18nEngImpl implements I18n {//內(nèi)容同上,此處省略}總結(jié)
以上是生活随笔為你收集整理的spring-boot注解详解(六)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 将.npy文件转.txt文件
- 下一篇: 语料库