Java
的註解在實際項目中使用得很是的多,特別是在使用了Spring
以後。
本文會介紹Java
註解的語法,以及在Spring
中使用註解的例子。
以Junit
中的@Test
註解爲例java
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Test { long timeout() default 0L; }
能夠看到@Test
註解上有@Target()
和@Retention()
兩個註解。
這種註解了註解的註解,稱之爲元註解。
跟聲明瞭數據的數據,稱爲元數據是一種意思。git
以後的註解的格式是github
修飾符 @interface 註解名 { 註解元素的聲明1 註解元素的聲明2 }
註解的元素聲明有兩種形式spring
type elementName(); type elementName() default value; // 帶默認值
@Target
註解@Target
註解用於限制註解能在哪些項上應用,沒有加@Target
的註解能夠應用於任何項上。數組
在java.lang.annotation.ElementType
類中能夠看到全部@Target
接受的項app
TYPE
在【類、接口、註解】上使用FIELD
在【字段、枚舉常量】上使用METHOD
在【方法】上使用PARAMETER
在【參數】上使用CONSTRUCTOR
在【構造器】上使用LOCAL_VARIABLE
在【局部變量】上使用ANNOTATION_TYPE
在【註解】上使用PACKAGE
在【包】上使用TYPE_PARAMETER
在【類型參數】上使用 Java 1.8 引入TYPE_USE
在【任何聲明類型的地方】上使用 Java 1.8 引入@Test
註解只容許在方法上使用。框架
@Target(ElementType.METHOD) public @interface Test { ... }
若是要支持多項,則傳入多個值。ide
@Target({ElementType.TYPE, ElementType.METHOD}) public @interface MyAnnotation { ... }
此外元註解也是註解,也符合註解的語法,如@Target
註解。@Target(ElementType.ANNOTATION_TYPE)
代表@Target
註解只能使用在註解上。函數
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { ElementType[] value(); }
@Retention
註解@Retention
指定註解應該保留多長時間,默認是RetentionPolicy.CLASS
。
在java.lang.annotation.RetentionPolicy
可看到全部的項工具
SOURCE
不包含在類文件中CLASS
包含在類文件中,不載入虛擬機RUNTIME
包含在類文件中,由虛擬機載入,能夠用反射API獲取@Test
註解會載入到虛擬機,能夠經過代碼獲取
@Retention(RetentionPolicy.RUNTIME) public @interface Test { ... }
@Documented
註解主要用於歸檔工具識別。被註解的元素能被Javadoc
或相似的工具文檔化。
@Inherited
註解添加了@Inherited
註解的註解,所註解的類的子類也將擁有這個註解
註解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface MyAnnotation { ... }
父類
@MyAnnotation class Parent { ... }
子類Child
會把加在Parent
上的@MyAnnotation
繼承下來
class Child extends Parent { ... }
@Repeatable
註解Java 1.8 引入的註解,標識註解是可重複使用的。
註解1
public @interface MyAnnotations { MyAnnotation[] value(); }
註解2
@Repeatable(MyAnnotations.class) public @interface MyAnnotation { int value(); }
有使用@Repeatable()
時的使用
@MyAnnotation(1) @MyAnnotation(2) @MyAnnotation(3) public class MyTest { ... }
沒使用@Repeatable()
時的使用,@MyAnnotation
去掉@Repeatable
元註解
@MyAnnotations({ @MyAnnotation(1), @MyAnnotation(2), @MyAnnotation(3)}) public class MyTest { ... }
這個註解仍是很是有用的,讓咱們的代碼變得簡潔很多,Spring
的@ComponentScan
註解也用到這個元註解。
byte
,short
,char
,int
,long
,float
,double
,boolean
)String
Class
enum
枚舉類
public enum Status { GOOD, BAD }
註解1
@Target(ElementType.ANNOTATION_TYPE) public @interface MyAnnotation1 { int val(); }
註解2
@Target(ElementType.TYPE) public @interface MyAnnotation2 { boolean boo() default false; Class<?> cla() default Void.class; Status enu() default Status.GOOD; MyAnnotation1 anno() default @MyAnnotation1(val = 1); String[] arr(); }
使用時,無默認值的元素必須傳值
@MyAnnotation2( cla = String.class, enu = Status.BAD, anno = @MyAnnotation1(val = 2), arr = {"a", "b"}) public class MyTest { ... }
Java
內置的註解@Override
註解告訴編譯器這個是個覆蓋父類的方法。若是父類刪除了該方法,則子類會報錯。
@Deprecated
註解表示被註解的元素已被棄用。
@SuppressWarnings
註解告訴編譯器忽略警告。
@FunctionalInterface
註解Java 1.8 引入的註解。該註釋會強制編譯器javac
檢查一個接口是否符合函數接口的標準。
有兩種比較特別的註解
@XxxAnnotation
, 不須要加括號value
,使用時直接傳值,不須要指定元素名@XxxAnnotation(100)
Java
的AnnotatedElement
接口中有getAnnotation()
等獲取註解的方法。
而Method
,Field
,Class
,Package
等類均實現了這個接口,所以均有獲取註解的能力。
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD}) public @interface MyAnno { String value(); }
@MyAnno("class") public class MyClass { @MyAnno("feild") private String str; @MyAnno("method") public void method() { } }
public class Test { public static void main(String[] args) throws Exception { MyClass obj = new MyClass(); Class<?> clazz = obj.getClass(); // 獲取對象上的註解 MyAnno anno = clazz.getAnnotation(MyAnno.class); System.out.println(anno.value()); // 獲取屬性上的註解 Field field = clazz.getDeclaredField("str"); anno = field.getAnnotation(MyAnno.class); System.out.println(anno.value()); // 獲取方法上的註解 Method method = clazz.getMethod("method"); anno = method.getAnnotation(MyAnno.class); System.out.println(anno.value()); } }
Spring
中使用自定義註解註解自己不會有任何的做用,須要有其餘代碼或工具的支持纔有用。
設想現有這樣的需求,程序須要接收不一樣的命令CMD
,
而後根據命令調用不一樣的處理類Handler
。
很容易就會想到用Map
來存儲命令和處理類的映射關係。
因爲項目多是多個成員共同開發,不一樣成員實現各自負責的命令的處理邏輯。
所以但願開發成員只關注Handler
的實現,不須要主動去Map
中註冊CMD
和Handler
的映射。
最終但願看到效果是這樣的
@CmdMapping(Cmd.LOGIN) public class LoginHandler implements ICmdHandler { @Override public void handle() { System.out.println("handle login request"); } } @CmdMapping(Cmd.LOGOUT) public class LogoutHandler implements ICmdHandler { @Override public void handle() { System.out.println("handle logout request"); } }
開發人員增長本身的Handler
,只須要建立新的類並註上@CmdMapping(Cmd.Xxx)
便可。
具體的實現是使用Spring
和一個自定義的註解
定義@CmdMapping
註解
@Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Component public @interface CmdMapping { int value(); }
@CmdMapping
中有一個int
類型的元素value
,用於指定CMD
。這裏作成一個單值註解。
這裏還加了Spring
的@Component
註解,所以註解了@CmdMapping
的類也會被Spring建立實例。
而後是CMD
接口,存儲命令。
public interface Cmd { int REGISTER = 1; int LOGIN = 2; int LOGOUT = 3; }
以後是處理類接口,現實狀況接口會複雜得多,這裏簡化了。
public interface ICmdHandler { void handle(); }
上邊說過,註解自己是不起做用的,須要其餘的支持。下邊就是讓註解生效的部分了。
使用時調用handle()
方法便可。
@Component public class HandlerDispatcherServlet implements InitializingBean, ApplicationContextAware { private ApplicationContext context; private Map<Integer, ICmdHandler> handlers = new HashMap<>(); public void handle(int cmd) { handlers.get(cmd).handle(); } public void afterPropertiesSet() { String[] beanNames = this.context.getBeanNamesForType(Object.class); for (String beanName : beanNames) { if (ScopedProxyUtils.isScopedTarget(beanName)) { continue; } Class<?> beanType = this.context.getType(beanName); if (beanType != null) { CmdMapping annotation = AnnotatedElementUtils.findMergedAnnotation( beanType, CmdMapping.class); if(annotation != null) { handlers.put(annotation.value(), (ICmdHandler) context.getBean(beanType)); } } } } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = applicationContext; } }
主要工做都是Spring
作,這裏只是將實例化後的對象put
到Map
中。
測試代碼
@ComponentScan("pers.custom.annotation") public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class); HandlerDispatcherServlet servlet = context.getBean(HandlerDispatcherServlet.class); servlet.handle(Cmd.REGISTER); servlet.handle(Cmd.LOGIN); servlet.handle(Cmd.LOGOUT); context.close(); } }
能夠看到使用註解可以寫出很靈活的代碼,註解也特別適合作爲使用框架的一種方式。因此學會使用註解仍是頗有用的,畢竟這對於上手框架或實現本身的框架都是很是重要的知識。