Java的代理和注解

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



今天打算寫寫關(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)代理,說實(shí)話,Android在使用代理類這方面用的并不是很多,關(guān)于AOP(面向切面編程)這些東西使用頻繁的還是在后臺開發(fā)。

代理的三個條件:

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

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

共同接口:

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

真實(shí)對象:

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)代理模式可以很方便的擴(kuò)展原有的功能并且不去修改原代碼,但是對于需要代理的多個類此時實(shí)現(xiàn)卻有些麻煩。為了簡單有效的解決這個問題,我們可以使用動態(tài)代理來更靈活的方式去實(shí)現(xiàn)代理功能。
使用動態(tài)代理的方式也比較簡單,Java提供給我們Proxy.newProxyInstance可以快速方便實(shí)現(xiàn)動態(tài)代理:

// 還是原來的代碼
Action realDo = new RealDoAction();
Action action = (Action) Proxy.newProxyInstance(Action.class.getClassLoader(), // 類加載器
        new Class[]{Action.class}, // 被代理類實(shí)現(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 注解可以保留到程序運(yùn)行的時候,它會被加載進(jìn)入到 JVM 中,所以在程序運(yùn)行時可以獲取到它們。

關(guān)于RetentionPolicy.SOURCE和RetentionPolicy.CLASS這兩者都可以使用AbstractProcessor來在編譯時生成代碼,其主要不同點(diǎn)在于編譯后的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)行注解。能在局部變量、泛型類、父類和接口的實(shí)現(xiàn)處使用,甚至能在方法上聲明異常的地方使用。(具體用途不詳。。)

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

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

2.1 編譯時注解

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

  1. 創(chuàng)建注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS) // 這里也可以改為RetentionPolicy.SOURCE,只不過在class文件中看不到這個注解
public @interface TestAnnotation {
    String value() default "";
}
  1. 繼承AbstractProcessor實(shí)現(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)入項(xiàng)目中,并且使用我們剛才創(chuàng)建的注解,這里一定要注意把原項(xiàng)目定義的注解刪掉,使用jar中的注解,不然不會生成代碼:

    添加注解.png

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

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

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

2.2 運(yùn)行時注解

運(yùn)行時注解就比較簡單了,下面還是看下例子:

@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());
        }
    }
}

運(yùn)行輸出:

123456789

好像沒什么可以說的,基于運(yùn)行時注解的框架也不少,例如EventBus、Retrofit等等。

The end

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


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容