Java注解实践
Java注解實(shí)踐
標(biāo)簽 : Java基礎(chǔ)
注解對(duì)代碼的語意沒有直接影響, 他們只負(fù)責(zé)提供信息給相關(guān)的程序使用. 注解永遠(yuǎn)不會(huì)改變被注解代碼的含義, 但可以通過工具對(duì)被注解的代碼進(jìn)行特殊處理.
JDK 基本Annotation
| @Override | 重寫 |
| @Deprecated | 已過時(shí) |
| @SuppressWarnings(value = "unchecked") | 壓制編輯器警告 |
| @SafeVarargs | 修飾”堆污染”警告 |
| @FunctionalInterface | Java8特有的函數(shù)式接口 |
- value特權(quán)
如果使用注解時(shí)只需要為value成員變量指定值, 則使用注解時(shí)可以直接在該注解的括號(hào)中指定value值, 而無需使用name=value的形式. 如@SuppressWarnings("unchecked")(SuppressWarnings的各種參數(shù)
請(qǐng)參考解析 @SuppressWarnings的各種參數(shù)) - 請(qǐng)堅(jiān)持使用@Override注解: 如果在每個(gè)方法中使用Override注解來聲明要覆蓋父類聲明, 編譯器就可以替你防止大量的錯(cuò)誤.
JDK 元Annotation
元Annotation用于修飾其他的Annotation定義.
| @Retention | 注解保留策略 |
| @Target | 注解修飾目標(biāo) |
| @Documented | 注解文檔提取 |
| @Inherited | 注解繼承聲明 |
- @Retention 注解的保留策略
value為SOURCE, CLASS, RUNTIME三值之一:
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 }- @Target 指定Annotation可以放置的位置(被修飾的目標(biāo))
- @Documented 指定被修飾的該Annotation可以被javadoc工具提取成文檔.
- @Inherited 指定被修飾的Annotation將具有繼承性
如果某個(gè)類使用@Xxx注解(該Annotation使用了@Inherited修飾)修飾, 則其子類自動(dòng)被@Xxx注解修飾.
Annotation
/*** Created by jifang on 15/12/22.*/ @Inherited @Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Testable { }Client
public class Client {@Testpublic void client(){new SubClass();} }@Testable class SupperClass{ }class SubClass extends SupperClass{public SubClass() {for (Annotation annotation : SubClass.class.getAnnotations()){System.out.println(annotation);}} }自定義注解
- 根據(jù)Annotation是否包含成員變量,可以把Annotation分為兩類:
- 標(biāo)記Annotation: 沒有成員變量的Annotation; 這種Annotation僅利用自身的存在與否來提供信息;
- 元數(shù)據(jù)Annotation: 包含成員變量的Annotation; 它們可以接受(和提供)更多的元數(shù)據(jù);
- 定義新注解使用@interface關(guān)鍵字, 其定義過程與定義接口非常類似(見上面的@Testable), 需要注意的是:Annotation的成員變量在Annotation定義中是以無參的方法形式來聲明的, 其方法名和返回值類型定義了該成員變量的名字和類型, 而且我們還可以使用default關(guān)鍵字為這個(gè)成員變量設(shè)定默認(rèn)值.
- 自定義的Annotation繼承了Annotation這個(gè)接口, 因此自定義注解中包含了Annotation接口中所有的方法;
提取Annotation信息
- 使用Annotation修飾了類/方法/成員變量等之后,這些Annotation不會(huì)自己生效,必須由這些注解的開發(fā)者提供相應(yīng)的工具來提取并處理Annotation信息(當(dāng)然,只有當(dāng)定義Annotation時(shí)使用了@Retention(RetentionPolicy.RUNTIME)修飾,JVM才會(huì)在裝載class文件時(shí)提取保存在class文件中的Annotation,該Annotation才會(huì)在運(yùn)行時(shí)可見,這樣我們才能夠解析).
- Java使用Annotation接口來代表程序元素前面的注解, 用AnnotatedElement接口代表程序中可以接受注解的程序元素.像Class Constructor Field Method Package這些類都實(shí)現(xiàn)了AnnotatedElement接口.
這樣, 我們只需要獲取到Class Method Filed等這些實(shí)現(xiàn)了AnnotatedElement接口的類實(shí)例, 就可以獲取到我們想要的注解信息了.
/*** Created by jifang on 15/12/22.*/ public class Client {@Testpublic void client() throws NoSuchMethodException {Annotation[] annotations = this.getClass().getMethod("client").getAnnotations();for (Annotation annotation : annotations) {System.out.println(annotation.annotationType().getName());}} }如果需要獲取某個(gè)注解中的元數(shù)據(jù),則需要強(qiáng)轉(zhuǎn)成所需的注解類型,然后通過注解對(duì)象的抽象方法來訪問這些元數(shù)據(jù):
@Tag(name = "client") public class Client {@Testpublic void client() throws NoSuchMethodException {Annotation[] annotations = this.getClass().getAnnotations();for (Annotation annotation : annotations) {if (annotation instanceof Tag) {Tag tag = (Tag) annotation;System.out.println("name: " + tag.name());System.out.println("description: " + tag.description());}}} }模擬Junit框架
我們用@Testable標(biāo)記哪些方法是可測(cè)試的, 只有被@Testable修飾的方法才可以被執(zhí)行.
/*** Created by jifang on 15/12/27.*/ @Inherited @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Testable { }如下定義TestCase測(cè)試用例定義了6個(gè)方法, 其中有4個(gè)被@Testable修飾了:
public class TestCase {@Testablepublic void test1() {System.out.println("test1");}public void test2() throws IOException {System.out.println("test2");throw new IOException("我test2出錯(cuò)啦...");}@Testablepublic void test3() {System.out.println("test3");throw new RuntimeException("我test3出錯(cuò)啦...");}public void test4() {System.out.println("test4");}@Testablepublic void test5() {System.out.println("test5");}@Testablepublic void test6() {System.out.println("test6");} }為了讓程序中的這些注解起作用, 必須為這些注解提供一個(gè)注解處理工具.
/*** Created by jifang on 15/12/27.*/ public class TestableProcessor {public static void process(String clazz) throws ClassNotFoundException, IllegalAccessException, InstantiationException {int passed = 0;int failed = 0;Object obj = Class.forName(clazz).newInstance();for (Method method : Class.forName(clazz).getMethods()) {if (method.isAnnotationPresent(Testable.class)) {try {method.invoke(obj);++passed;} catch (IllegalAccessException | InvocationTargetException e) {System.out.println("method " + method.getName() + " execute error: < " + e.getCause() + " >");e.printStackTrace(System.out);++failed;}}}System.out.println("共運(yùn)行" + (failed + passed) + "個(gè)方法, 成功" + passed + "個(gè), 失敗" + failed + "個(gè)");}public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {TestableProcessor.process("com.feiqing.annotation.TestCase");} }拋出特定異常
前面介紹的只是一個(gè)標(biāo)記Annotation,程序通過判斷Annotation是否存在來決定是否運(yùn)行指定方法,現(xiàn)在我們要針對(duì)只在拋出特殊異常時(shí)才成功添加支持,這樣就用到了具有成員變量的注解了:
/*** Created by jifang on 15/12/28.*/ @Inherited @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface TestableException {Class<? extends Throwable>[] value(); }- TestCase
- 注解處理器
注解添加監(jiān)聽器
下面通過使用Annotation簡(jiǎn)化事件編程, 在傳統(tǒng)的代碼中總是需要通過addActionListener方法來為事件源綁定事件監(jiān)聽器:
/*** Created by jifang on 15/12/27.*/ public class SwingPro {private JFrame mainWin = new JFrame("使用注解綁定事件監(jiān)聽器");private JButton ok = new JButton("確定");private JButton cancel = new JButton("取消");public void init() {JPanel jp = new JPanel();// 為兩個(gè)按鈕設(shè)置監(jiān)聽事件ok.addActionListener(new OkListener());cancel.addActionListener(new CancelListener());jp.add(ok);jp.add(cancel);mainWin.add(jp);mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);mainWin.pack();mainWin.setVisible(true);}public static void main(String[] args) {new SwingPro().init();} }class OkListener implements ActionListener {@Overridepublic void actionPerformed(ActionEvent e) {JOptionPane.showMessageDialog(null, "你點(diǎn)擊了確認(rèn)按鈕!");} }class CancelListener implements ActionListener {@Overridepublic void actionPerformed(ActionEvent e) {JOptionPane.showMessageDialog(null, "你點(diǎn)擊了取消按鈕!");} }下面我們?cè)撚米⒔饨壎ūO(jiān)聽器:
- 首先, 我們需要自定義一個(gè)注解
- 然后還要一個(gè)注解處理器
- 主程序(注意注釋處)
重復(fù)注解
在Java5到Java7這段時(shí)間里, 同一個(gè)程序元素前只能使用一個(gè)相同類型的Annotation; 如果需要在同一個(gè)元素前使用多個(gè)相同的Annotation, 則必須使用Annotation容器(在Java8中, 對(duì)這種情況做了改善, 但其實(shí)也只是一種寫法上的簡(jiǎn)化, 其本質(zhì)還是一樣的).由于在實(shí)際開發(fā)中,Java8還未大面積的使用, 因此在此只介紹Java7中重復(fù)注解定義與使用.
- Table Annotation定義(代表數(shù)據(jù)庫表)
- Table 容器
注意: 容器注解的保留期必須比它所包含的注解的保留期更長, 否則JVM會(huì)丟棄容器, 相應(yīng)的注解也就丟失了.
- Client
使用時(shí)需要用Table容器來盛裝Table注解
在Java8中, 可以直接使用
@Table(name = "t_user", description = "用戶表") @Table(name = "t_feed", description = "動(dòng)態(tài)表")的形式來注解Client, 但@Tables還是需要開發(fā)者來寫的, 由此可以看出, 重復(fù)注解只是一種簡(jiǎn)化寫法, 這種寫法只是一種假象: 多個(gè)重復(fù)注解其實(shí)會(huì)被作為容器注解的value成員.
參考:
總結(jié)
- 上一篇: 水平集分割
- 下一篇: PHPWind的版权等信息去除的方法