Java的代理和注解

都已經(jīng)記不起到底何時開始使用Java語言進(jìn)行編程的了裁厅,印象中起碼也有三五年了吧拾徙。想象中自己應(yīng)該要做后臺滞诺,卻陰差陽錯成了Android狗这刷,還好Java仍然陪伴著我(Java:你明明去看了Kotlin3裎)鼻弧。



今天打算寫寫關(guān)于代理模式和注解這方面的內(nèi)容。這兩個簡直是風(fēng)馬牛不相及锦茁,放在一起好像有那么一丟丟別扭攘轩。不過呢,這主要是為了后面的文章做準(zhǔn)備码俩。作為一名偽源碼愛好者度帮,還是讀過不少源碼。其中Android的網(wǎng)絡(luò)請求框架Retrofit就將動態(tài)代理和注解完美了結(jié)合在了一起稿存,后面詳細(xì)講解吧(PS:這些內(nèi)容一年前就已經(jīng)看懂了笨篷,現(xiàn)在只為了記錄)!

1. 代理模式

代理可以分為兩類:靜態(tài)代理和動態(tài)代理瓣履,說實話率翅,Android在使用代理類這方面用的并不是很多,關(guān)于AOP(面向切面編程)這些東西使用頻繁的還是在后臺開發(fā)袖迎。

代理的三個條件:

  • 共同接口:代理類和被代理類實現(xiàn)相同的接口
  • 真實對象:實現(xiàn)接口安聘,并真正完成某些操作
  • 代理對象:實現(xiàn)接口痰洒,對真實對象進(jìn)行代理,可以在其操作前后執(zhí)行其他操作

關(guān)于代理我的理解就是xxx(被代理類)有中間商(代理類)浴韭,還可能賺差價(進(jìn)行操作)丘喻。光說不練好像一點用處都沒有,還是用show you my code吧念颈!

共同接口:

public interface Action {
    void doSomething(float value);
}

真實對象:

public class RealDoAction implements Action {
    @Override
    public void doSomething(float value) {
        System.out.println("我是車主我要賣車泉粉,價錢" + value + "元");
    }
}

靜態(tài)代理:

public class ActionProxy implements Action {

    private Action realDoAction;

    public ActionProxy(RealDoAction realDoAction) {
        this.realDoAction = realDoAction;
    }

    @Override
    public void doSomething(float value) {
        // 被代理類操作執(zhí)行前執(zhí)行的操作
        System.out.println("我是平臺,在我這里可以進(jìn)行交易榴芳。");
        realDoAction.doSomething(value);
        // 被代理類操作完成后執(zhí)行的操作
        System.out.println("車賣出去了嗡靡,價錢" + addValue(value) + "元,沒有中間商賺差價窟感。");
    }

    private float addValue(float value) {
        return value + 10;
    }
}
// 測試
public static void main(String[] args) {
    Action action = new ActionProxy(new RealDoAction());
    action.doSomething(100);
}

輸出:

我是平臺讨彼,在我這里可以進(jìn)行交易。
我是車主我要賣車柿祈,價錢100.0元
車賣出去了哈误,價錢110.0元,沒有中間商賺差價躏嚎。

可以看到使用靜態(tài)代理模式可以很方便的擴展原有的功能并且不去修改原代碼蜜自,但是對于需要代理的多個類此時實現(xiàn)卻有些麻煩。為了簡單有效的解決這個問題卢佣,我們可以使用動態(tài)代理來更靈活的方式去實現(xiàn)代理功能重荠。
使用動態(tài)代理的方式也比較簡單,Java提供給我們Proxy.newProxyInstance可以快速方便實現(xiàn)動態(tài)代理:

// 還是原來的代碼
Action realDo = new RealDoAction();
Action action = (Action) Proxy.newProxyInstance(Action.class.getClassLoader(), // 類加載器
        new Class[]{Action.class}, // 被代理類實現(xiàn)的所有接口
        new InvocationHandler() { // InvocationHandler具體邏輯寫在這里虚茶,返回值是該方法的返回值戈鲁。接口中有多個方法可以根據(jù)方法名稱判斷
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("我是平臺,在我這里可以進(jìn)行交易嘹叫。");
                method.invoke(realDo, args);
                for (int i = 0; i < args.length; i++) {
                    if (args[i] instanceof Float) {
                        System.out.println("車賣出去了荞彼,價錢" + ((float) args[i] + 10) + "元,沒有中間商賺差價待笑。");
                        break;
                    }
                }
                return null;
            }
        });
action.doSomething(1000);

看下輸出鸣皂,和靜態(tài)代理一樣:

我是平臺,在我這里可以進(jìn)行交易暮蹂。
我是車主我要賣車寞缝,價錢1000.0元
車賣出去了,價錢1010.0元仰泻,沒有中間商賺差價荆陆。

代理到這里基本上就結(jié)束了,東西不多集侯。


2. 注解

談起注解被啼,可寫的內(nèi)容可就很多了帜消。這里還是簡單來寫吧,省的又是長篇大論浓体。
首先說下可以用在注解上的注解——元注解

@Retention保留期泡挺,可取值:

  • RetentionPolicy.SOURCE 注解只在源碼階段保留,編譯后的class文件不存在該注解命浴。
  • RetentionPolicy.CLASS 注解只被保留到編譯進(jìn)行的時候娄猫,編譯后的class文件該注解仍然存在,但是不會被加載到JVM中生闲。
  • RetentionPolicy.RUNTIME 注解可以保留到程序運行的時候媳溺,它會被加載進(jìn)入到 JVM 中,所以在程序運行時可以獲取到它們碍讯。

關(guān)于RetentionPolicy.SOURCE和RetentionPolicy.CLASS這兩者都可以使用AbstractProcessor來在編譯時生成代碼悬蔽,其主要不同點在于編譯后的class文件中是否存在該注解。也可以理解為是方便給IDE看還是給程序員看捉兴。

@Target注解的作用域蝎困,可取值:

  • ElementType.ANNOTATION_TYPE 可以給一個注解進(jìn)行注解
  • ElementType.CONSTRUCTOR 可以給構(gòu)造方法進(jìn)行注解
  • ElementType.FIELD 可以給屬性進(jìn)行注解
  • ElementType.LOCAL_VARIABLE 可以給局部變量進(jìn)行注解
  • ElementType.METHOD 可以給方法進(jìn)行注解
  • ElementType.PACKAGE 可以給一個包進(jìn)行注解
  • ElementType.PARAMETER 可以給一個方法內(nèi)的參數(shù)進(jìn)行注解
  • ElementType.TYPE 可以給一個類型進(jìn)行注解,比如類轴术、接口难衰、枚舉
  • ElementType.TYPE_PARAMETER和ElementType.TYPE_USE(1.8新增)它們都可以限制哪個類型可以進(jìn)行注解弯囊。能在局部變量岂津、泛型類吕晌、父類和接口的實現(xiàn)處使用,甚至能在方法上聲明異常的地方使用彼宠。(具體用途不詳。弟塞。)

@Documented有關(guān)注釋的注解凭峡。
@Inherited指可繼承注解,表示某個類的父類擁有該注解决记,子類如果沒有其他注解的話摧冀,那么該子類會繼承父類的注解。
@Repeatable(1.8新增)我的理解是為了簡化一個屬性擁有多個相同注解而新增的注解系宫,這里面可以放多個注解索昂。(解釋不太清楚,用的也不多)

上面講了5個元注解扩借,其實常用的也就@Retention和@Target椒惨,這里面可以根據(jù)@Retention可以分為編譯時注解和運行時注解,下面分別寫個例子潮罪。

2.1 編譯時注解

關(guān)于編譯時注解昨天基本上花費了一整天康谆,原本Eclipse中的可以執(zhí)行但是換成了IDEA就不能使用了领斥。花費了一天時間沃暗,查找原因(主要還是不想用老大哥Eclipse進(jìn)行展示)月洛。下面看下例子:

  1. 創(chuàng)建注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS) // 這里也可以改為RetentionPolicy.SOURCE,只不過在class文件中看不到這個注解
public @interface TestAnnotation {
    String value() default "";
}
  1. 繼承AbstractProcessor實現(xiàn)編譯時生成代碼
@SupportedAnnotationTypes("com.nick.demo.annotation.TestAnnotation")// 要支持的注解
@SupportedSourceVersion(SourceVersion.RELEASE_8) // 版本
public class Processor extends AbstractProcessor {
    // 文件創(chuàng)建器
    private Filer filer;
    // 消息輸出者
    private Messager messager;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        // 初始化獲取文件創(chuàng)建器和消息輸出
        filer = processingEnv.getFiler();
        messager = processingEnv.getMessager();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // 根據(jù)注解獲取所有的Element
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(TestAnnotation.class);
        // 遍歷
        for (Element element : elements) {
            // 如果類型是Class描睦,也就是注解的作用域是ElementType.TYPE
            if (element.getKind() == ElementKind.CLASS) {
                // 轉(zhuǎn)換成TypeElement
                TypeElement typeElement = (TypeElement) element;
                Name simpleName = typeElement.getSimpleName();
                // 輸出名稱膊存,只為測試而用
                messager.printMessage(Diagnostic.Kind.NOTE, simpleName);
                // 輸出全部名稱
                messager.printMessage(Diagnostic.Kind.NOTE, typeElement.getQualifiedName());
                // 創(chuàng)建文件
                createFile(typeElement.getQualifiedName().toString(), element.getAnnotation(TestAnnotation.class).value());
            }
        }
        return true;
    }

    private void createFile(String className, String output) {
        StringBuilder cls = new StringBuilder();
        // 獲得com.xxx.A的包名,com.xxx
        String[] strings = className.split("\\.");
        StringBuilder packageName = new StringBuilder();
        for (int i = 0; i < strings.length - 1; i++) {
            packageName.append(strings[i]);
            if (i != strings.length - 2) {
                packageName.append(".");
            }
        }
        // 代碼的拼接
        cls.append("package " + packageName + ";\n\npublic class ")
                .append(strings[strings.length - 1] + output)
                .append(" {\n  public static void main(String[] args) {\n")
                .append("    System.out.println(\"")
                .append(output)
                .append("\");\n  }\n}");
        // 輸出這段代碼
        messager.printMessage(Diagnostic.Kind.NOTE, cls.toString());
        try {
            // 通過文件創(chuàng)建器創(chuàng)建SourceFile
            JavaFileObject sourceFile = filer.createSourceFile(className + output);
            Writer writer = sourceFile.openWriter();
            writer.write(cls.toString());
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
  1. 在與java平級目錄下創(chuàng)建resources文件夾忱叭,并在其目錄下創(chuàng)建META-INF文件隔崎,接著在META-INF創(chuàng)建services,最后在services文件目錄下創(chuàng)建文件javax.annotation.processing.Processor韵丑,內(nèi)容為自定義的Processer的類全名爵卒。如下圖所示:


    內(nèi)容.png
添加Processer.png
  1. 將java文件和resources文件打包成jar,idea可以通過Project Structure-> Artifacts 添加JAR-> From module撵彻,選擇module并且記得將resources也添加:
生成jar.png
  1. 這一步很關(guān)鍵钓株,我們創(chuàng)建了編譯時注解的代碼的jar,我們需要告訴ide在編譯的時候執(zhí)行我這個jar陌僵,所以需要將其添加到Compiler的Annotation Processors中:
添加.png
  1. 將jar導(dǎo)入項目中轴合,并且使用我們剛才創(chuàng)建的注解,這里一定要注意把原項目定義的注解刪掉碗短,使用jar中的注解受葛,不然不會生成代碼:

    添加注解.png

  2. 編譯,可以看到Messages中有我們打印的日志偎谁。編譯成功可以在build/classes/main/目錄下找到自定義注解生成的class文件总滩,同時在generated目錄下還有java文件的生成:

打印了message.png
生成的文件.png

OK,編譯時注解這里基本上就講完了巡雨。Android好多優(yōu)秀的框架都使用的編譯時注解框架闰渔,例如Android的Databinding框架,butterknife等等铐望。

2.2 運行時注解

運行時注解就比較簡單了冈涧,下面還是看下例子:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    String value() default "";
}

@TestAnnotation("123456789")
public class Main {
    public static void main(String[] args) {
        // 獲得類的TestAnnotation注解
        TestAnnotation annotation = Main.class.getAnnotation(TestAnnotation.class);
        if (annotation != null) {
            // 將值輸出
            System.out.println(annotation.value());
        }
    }
}

運行輸出:

123456789

好像沒什么可以說的,基于運行時注解的框架也不少正蛙,例如EventBus督弓、Retrofit等等跟畅。

The end

寫完收工咽筋,關(guān)于代理和注解就寫這么多吧。希望能在年前將Retrofit的分析寫完奸攻,加油了辐赞。


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末窖梁,一起剝皮案震驚了整個濱河市邀窃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌假哎,老刑警劉巖瞬捕,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異舵抹,居然都是意外死亡,警方通過查閱死者的電腦和手機惧蛹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門爵政,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人饱岸,你說我怎么就攤上這事徽千∷椋” “怎么了牍汹?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長锨并。 經(jīng)常有香客問我第煮,道長包警,這世上最難降的妖魔是什么底靠? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任篱瞎,我火速辦了婚禮俐筋,結(jié)果婚禮上澄者,老公的妹妹穿的比我還像新娘粱挡。我一直安慰自己询筏,他們只是感情好嫌套,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布踱讨。 她就那樣靜靜地躺著痹筛,像睡著了一般帚稠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蔓挖,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機與錄音拷获,去河邊找鬼减细。 笑死未蝌,一個胖子當(dāng)著我的面吹牛萧吠,可吹牛的內(nèi)容都是我干的纸型。 我是一名探鬼主播狰腌,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼琼腔,長吁一口氣:“原來是場噩夢啊……” “哼躁垛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起擂达,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤板鬓,失蹤者是張志新(化名)和其女友劉穎俭令,沒想到半個月后抄腔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绵患,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡落蝙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片病瞳。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出戏溺,到底是詐尸還是另有隱情屠尊,我是刑警寧澤托享,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布赃绊,位于F島的核電站,受9級特大地震影響校仑,放射性物質(zhì)發(fā)生泄漏档痪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望汽绢。 院中可真熱鬧,春花似錦积仗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽阶女。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間消别,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工樊拓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓蚊锹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親稚瘾。 傳聞我的和親對象是個殘疾皇子牡昆,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容