Android coder 須要理解的註解、反射和動態代理

註解咱們常用它,不少框架也提供了不少註解給咱們使用,如 ARouter@Route(path = "/test/activity")butterknife@BindView(R.id.user) EditText username; 等,可是,你有沒有自定義過註解,寫過本身的註解處理器呢?反射聽起來很高大上,可是實際上你真的瞭解他以後,只是一些API的調用而已;動態代理其實只是在靜態代理(代理模式)基礎上使用了反射技術;本篇文章將帶領你們對註解、反射及動態代理有更清晰的認知。html


本篇文章的示例代碼放在 Github 上,全部知識點,如圖:java

註解

註解(Annotations),元數據的一種形式,提供有關於程序但不屬於程序自己的數據。註解對它們註解的代碼的操做沒有直接影響。android

註解有多種用途,例如:git

  • 爲編譯器提供信息:編譯器可使用註解來檢查錯誤或抑制警告
  • 編譯或部署時處理:能夠生成代碼、XML、文件等
  • 運行時處理:註解能夠在運行時檢查

註解的格式

註解的格式以下:github

@Persilee
class MyClass { ... }

註解已 @ 開頭後面跟上內容,註解能夠包含元素,例如:緩存

@Persilee(id=666, value = "lsy")
class MyClass { ... }

若是,只有一個 value 元素,則能夠省略該名稱,若是,沒有元素,則能夠省略括號,例如bash

@Persilee("lsy") // 只有一個 value 元素
class MyClass { ... }

@Persilee // 沒有元素
class MyClass { ... }

若是,註解有相同的類型,則是重複註解,如app

@Persilee("lsy")
@Persilee("zimu")
class MyClass { ... }

註解聲明

註解的定義相似於接口的定義,在關鍵字 interface 前加上 @,如:框架

@interface Persilee {
    int id();
    String value();
}

註解類型

int id()String value() 是註解類型(annotation type),它們也能夠定義可選的默認值,如:ide

@interface Persilee {
    int id();
    String value() default "lsy";
}

在使用註解時,若是定義的註解的註解類型沒有默認值,則必須進行賦值,如:

@Persilee(id = 666) // id 必需要賦值,如,@Persilee 會提示 id 必須賦值
class MyClass { ... }

元註解

在註解上面的註解稱爲元註解(meta-annotations),如

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
@interface Persilee {
    int id();
    String value() default "lsy";
}

java.lang.annotation 中定義了幾種元註解類型(常使用的是 @Retention、@Target),如

@Retention 指定註解的存儲方式,咱們由 RetentionPolicy.java (是一個枚舉)可知,如:

public enum RetentionPolicy {
    SOURCE, // 標記的註解僅保留在源級別中,並被編譯器忽略。
    CLASS, // 標記的註解在編譯時由編譯器保留,但 Java 虛擬機(JVM)會忽略。
    RUNTIME // 標記的註解由 JVM 保留,所以運行時環境可使用它。
}

@Target 指定註解可使用的範圍,咱們由 ElementType.java (是一個枚舉)可知使用範圍,以下:

public enum ElementType {
    TYPE, // 類
    FIELD, // 字段或屬性
    METHOD, // 方法
    PARAMETER, // 參數
    CONSTRUCTOR, // 構造方法
    LOCAL_VARIABLE, // 局部變量
    ANNOTATION_TYPE, // 也可使用在註解上
    PACKAGE, // 包
    TYPE_PARAMETER, // 類型參數
    TYPE_USE // 任何類型
}

對於 TYPE_PARAMETER (類型參數) 、 TYPE_USE (任何類型名稱) 可能不是很好理解,若是把 Target 設置成 @Target({ElementType.TYPE_PARAMETER}),表示可使用在泛型(上篇文章有介紹過泛型)的類型參數上,如:

public class TypeParameterClass<@Persilee T> {
    public <@Persilee T> T foo(T t) {
        return null;
    }
}

若是把 Target 設置成 @Target({ElementType.TYPE_USE}),表示可使用在任何類型上,如:

TypeParameterClass<@Persilee String> typeParameterClass = new TypeParameterClass<>();
@Persilee String text = (@Persilee String)new Object();

@Documented 註解表示使用了指定的註解,將使用 Javadoc 工具記錄這些元素。

@Inherited 註解表示註解類型能夠從超類繼承。

@Repeatable 註解代表標記的註解能夠屢次應用於同一聲明或類型使用。

註解應用場景

根據 @Retention 元註解定義的存儲方式,註解通常可使用在如下3種場景中,如:

級別 技術 說明
源碼 APT 在編譯期能獲取註解與註解聲明的類和類中全部成員信息,通常用於生成額外的輔助類。
字節碼       字節碼加強       在編譯出Class後,經過修改Class數據以實現修改代碼邏輯目的,對因而否須要修改的區分或者修改成不一樣邏輯的判斷可使用註解。
運行時 反射 在程序運行時,經過反射技術動態獲取註解與其元素,從而完成不一樣的邏輯判斷。

小案例(使用註解實現語法檢查)

咱們定義一個 weekDay 字段,類型是 WeekDay 枚舉類型,方便咱們設置枚舉中指定的值,如:

class WeekDayDemo {

    private static WeekDay weekDay;

    enum WeekDay {
        SATURDAY,SUNDAY
    }

    public static WeekDay getWeekDay() {
        return weekDay;
    }

    public static void setWeekDay(WeekDay weekDay) {
        WeekDayDemo.weekDay = weekDay;
    }

    public static void main(String[] args) {
        setWeekDay(WeekDay.SATURDAY);
        System.out.println(getWeekDay());
    }
}

衆所周知,在 Java 中枚舉的實質是特殊的靜態成員變量,在運行時候,全部的枚舉會做爲單例加載到內存中,很是消耗內存,那麼,有沒有什麼優化的方案呢,在此,咱們使用註解來取代枚舉。

咱們使用常量和 @intDef (語法檢查)元註解去代替枚舉,如:

class IntdefDemo {

    private static final int SATURDAY = 0;
    private static final int SUNDAY = 1;

    private static int weekDay;

    @IntDef({SATURDAY, SUNDAY})
    @Target({ElementType.FIELD, ElementType.PARAMETER})
    @Retention(RetentionPolicy.SOURCE)
    @interface WeekDay { //自定義一個 WeekDay 註解

    }

    public static void setWeekDay(@WeekDay int weekDay) { // 使用 WeekDay 註解限制參數類型
        IntdefDemo.weekDay = weekDay;
    }

    public static void main(String[] args) {
        setWeekDay(SATURDAY); // 只能 傳入 SATURDAY, SUNDAY
    }
}

APT註解處理器

APT(Annotation Processor Tools) 註解處理器,用於處理註解,編寫好的 Java 文件,須要通過 Javac 的編譯,編譯爲虛擬機可以加載的字節碼(Class)文件,註解處理器是 Javac 自帶的一個工具,用來在編譯時期處理註解信息。

上文中咱們已自定義好了 @Persilee 註解,下面咱們來編寫一個簡單的註解處理器來處理 @Persilee 註解,咱們能夠新建一個 Java 的 Module,建立一個 PersileeProcessor 的類,如:

@SupportedAnnotationTypes("net.lishaoy.anreprdemo.Persilee")  //指定要處理的註解
public class PersileeProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Messager messager = processingEnv.getMessager(); //
        messager.printMessage(Diagnostic.Kind.NOTE, "APT working ...");
        for (TypeElement typeElement: set) {
            messager.printMessage(Diagnostic.Kind.NOTE,"===>" + typeElement.getQualifiedName());
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(typeElement);
            for (Element element: elements) {
                messager.printMessage(Diagnostic.Kind.NOTE,"===>" + element.getSimpleName());
            }
        }

        return false;
    }
}

而後,在 main 目錄下新建 resources 目錄,如圖:

這個目錄結構是規定死的,必須這樣寫,而後在 javax.annotation.processing.Processor 文件裏註冊須要處理的註解處理器,如

net.lishaoy.aptlib.PersileeProcessor

最後,在 appbuild.gradle 文件引入模塊,如

dependencies {
  ...

  annotationProcessor project(':aptlib')
}

在你 Build 工程時候,會在 Task :app:compileDebugJavaWithJavac 任務打印咱們在註解處理程序的日誌信息,如:

注: APT working ...
注: ===>net.lishaoy.anreprdemo.Persilee
注: ===>MainActivity

由於,咱們只在 MainActivity 中使用了 @Persilee 註解,以下:

@Persilee(id = 666, value = "lsy")
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);
    }
}

反射

通常狀況下,咱們使用某個類時一定知道它是什麼類,用來作什麼的。因而咱們直接對這個類進行實例化,以後使用這個類對象進行操做。

Cook cook = new Cook(); // 實例化一個對象,標準用法
cook.cookService("🍅");

反射是一開始並不知道初始化的類對象是什麼,也不能使用 new 關鍵字來建立對象,反射是在運行的時才知道要操做的類是什麼,而且能夠在運行時獲取類的完整構造,調用對應的方法。

Java 反射機制主要提供瞭如下功能:

  • 在運行時構造任意一個類的對象
  • 在運行時獲取或修改任意一個類所具備的成員變量和方法
  • 在運行時調用任意一個對象的方法(屬性)

Class類

Class是一個類,封裝了當前對象所對應的類的信息,咱們寫的每個類均可以當作一個對象,是 java.lang.Class 類的對象,Class是用來描述類的類。

得到Class對象

Class對象的獲取有3種方式,以下:

  • 經過類名獲取 類名.class
  • 經過對象獲取 對象名.getClass()
  • 經過全類名獲取 Class.forName(全類名)
Cook cook = new Cook();
Class cookClass = Cook.class;
Class cookClass1 = cook.getClass();
Class cookClass2 = Class.forName("net.lishaoy.reflectdemo.Cook");

建立實例

咱們能夠經過反射來生成對象的實例,如:

Class cookClass = Cook.class;
Cook cook1 = (Cook) cookClass.newInstance();

獲取構造器

獲取構造器的方法有,以下:

  • Constructor getConstructor(Class[] params):得到使用特殊的參數類型的public構造函數(包括父類)
  • Constructor[] getConstructors():得到類的全部公共構造函數
  • Constructor getDeclaredConstructor(Class[] params):得到使用特定參數類型的構造函數(包括私有)
  • Constructor[] getDeclaredConstructors():得到類的全部構造函數(與接入級別無關)

咱們來新建一個 Person ,以便咱們的演示,如:

public class Person {

    public String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person() {
        super();
    }

    public String getName() {
        System.out.println("get name: " + name);
        return name;
    }

    public void setName(String name) {
        this.name = name;
        System.out.println("set name: " + this.name);
    }

    public int getAge() {
        System.out.println("get age: " + age);
        return age;
    }

    public void setAge(int age) {
        this.age = age;
        System.out.println("set age: " + this.age);
    }

    private void privateMethod(){
        System.out.println("the private method!");
    }
}

很常規的一個類,裏面有私有的屬性和方法。

下面,咱們新建一個 GetConstructor 的類來演示,獲取構造器方法如何使用,如:

class GetConstructor {

    public static void main(String[] args) throws
            ClassNotFoundException,
            NoSuchMethodException,
            IllegalAccessException,
            InvocationTargetException,
            InstantiationException {

        String className = "net.lishaoy.reflectdemo.entity.Person";
        Class<Person> personClass = (Class<Person>) Class.forName(className);

        //獲取所有的constructor對象
        Constructor<?>[] constructors = personClass.getConstructors();
        for (Constructor<?> constructor: constructors) {
            System.out.println("獲取所有的constructor對象: " + constructor);
        }

        //獲取某一個constructor對象
        Constructor<Person> constructor = personClass.getConstructor(String.class, int.class);
        System.out.println("獲取某一個constructor對象: " + constructor);

        //調用構造器的 newInstance() 方法建立對象
        Person person = constructor.newInstance("lsy", 66);
        System.out.println(person.getName() + ", " + person.getAge() );
    }

}

輸出結果,以下:

獲取所有的constructor對象: public net.lishaoy.reflectdemo.entity.Person(java.lang.String,int)
獲取所有的constructor對象: public net.lishaoy.reflectdemo.entity.Person()
獲取某一個constructor對象: public net.lishaoy.reflectdemo.entity.Person(java.lang.String,int)
lsy, 66

獲取方法

獲取方法的方法有,以下:

  • Method getMethod(String name, Class[] params):使用特定的參數類型,得到命名的公共方法
  • Method[] getMethods():得到類的全部公共方法
  • Method getDeclaredMethod(String name, Class[] params):使用特寫的參數類型,得到類聲明的命名的方法
  • Method[] getDeclaredMethods():得到類聲明的全部方法

咱們新建立一個 GetMethod 來演示如何來獲取和調用方法,如:

class GetMethod {

    public static void main(String[] args) throws
            ClassNotFoundException,
            NoSuchMethodException,
            IllegalAccessException,
            InstantiationException,
            InvocationTargetException {

        Class<?> aClass = Class.forName("net.lishaoy.reflectdemo.entity.Person");

        //獲取全部的public方法(包含從父類繼承的方法)
        Method[] methods = aClass.getMethods();
        for (Method method: methods) {
            System.out.println("獲取全部public方法: " + method.getName() + "()");
        }

        System.out.println("===========================");

        //獲取全部方法(不包含父類方法)
        methods = aClass.getDeclaredMethods();
        for (Method method: methods) {
            System.out.println("獲取全部方法: " + method.getName() + "()");
        }

        System.out.println("===========================");

        //獲取指定的方法
        Method method = aClass.getDeclaredMethod("setAge", int.class);
        System.out.println("獲取指定的方法:" + method);

        //調用方法
        Object instance = aClass.newInstance();
        method.invoke(instance, 66);

        //調用私有方法
        method = aClass.getDeclaredMethod("privateMethod");
        method.setAccessible(true); // 須要調用此方法且設置爲 true
        method.invoke(instance);

    }

}

運行結果,以下:

獲取全部public方法: getName()
獲取全部public方法: setName()
獲取全部public方法: setAge()
獲取全部public方法: getAge()
獲取全部public方法: wait()
獲取全部public方法: wait()
獲取全部public方法: wait()
獲取全部public方法: equals()
獲取全部public方法: toString()
獲取全部public方法: hashCode()
獲取全部public方法: getClass()
獲取全部public方法: notify()
獲取全部public方法: notifyAll()
===========================
獲取全部方法: getName()
獲取全部方法: setName()
獲取全部方法: setAge()
獲取全部方法: privateMethod()
獲取全部方法: getAge()
===========================
獲取指定的方法:public void net.lishaoy.reflectdemo.entity.Person.setAge(int)
set age: 66
the private method!

BUILD SUCCESSFUL in 395ms

獲取成員變量

獲取成員變量的方法有,以下:

  • Field getField(String name):得到命名的公共字段
  • Field[] getFields():得到類的全部公共字段
  • Field getDeclaredField(String name):得到類聲明的命名的字段
  • Field[] getDeclaredFields():得到類聲明的全部字段

咱們再來新建一個 GetField 的類來演示如何獲取成員變量,以下:

class GetField {

    public static void main(String[] args) throws
            ClassNotFoundException,
            NoSuchFieldException,
            IllegalAccessException,
            InstantiationException {

        Class<?> aClass = Class.forName("net.lishaoy.reflectdemo.entity.Person");

        // 獲取全部字段(不包含父類字段)
        Field[] fields = aClass.getDeclaredFields();
        for (Field field: fields) {
            System.out.println("獲取全部字段: " + field.getName());
        }

        System.out.println("================");

        // 獲取指定字段
        Field name = aClass.getDeclaredField("name");
        System.out.println("獲取指定字段: " + name.getName());

        // 設置指定字段的值
        Object instance = aClass.newInstance();
        name.set(instance, "per");

        // 獲取指定字段的值
        Object o = name.get(instance);
        System.out.println("獲取指定字段的值: " + o);

        // 設置和獲取私有字段的值
        Field age = aClass.getDeclaredField("age");
        age.setAccessible(true); // 須要調用此方法且設置爲 true
        age.set(instance, 66);
        System.out.println("獲取私有字段的值: " + age.get(instance));

    }

}

運行結果,以下:

獲取全部字段: name
獲取全部字段: age
================
獲取指定字段: name
獲取指定字段的值: per
獲取私有字段的值: 66

BUILD SUCCESSFUL in 395ms

使用註解和反射實現自動findViewById(案例)

咱們已經對註解和反射有了更清晰的認知,下面咱們經過一個小案例來鞏固咱們的學習:使用註解和反射完成相似 butterknife 的自動 findViewById 的功能。

新建一個空的 Android 工程,在工程目錄下新建 inject 目錄,在此目錄下新建一個 InjectView 的類和 BindView 的自定義註解,如:

建立InjectView

InjectView 類經過反射完成 findViewById 功能:

public class InjectView {

    public static void init(Activity activity) {
        // 獲取 activity 的 class 對象
        Class<? extends Activity> aClass = activity.getClass();
        // 獲取 activity 的因此成員變量
        Field[] declaredFields = aClass.getDeclaredFields();
        // 變量因此成員變量
        for (Field field: declaredFields) {
            // 判斷屬性是否加上了 @BindView 註解
            if(field.isAnnotationPresent(BindView.class)){
                // 獲取註解 BindView 對象
                BindView bindView = field.getAnnotation(BindView.class);
                // 獲取註解類型元素 id
                int id = bindView.value();
                // 經過資源 id 找到對應的 view
                View view = activity.findViewById(id);
                // 設置能夠訪問私有字段
                field.setAccessible(true);
                try {
                    // 給字段賦值
                    field.set(activity,view);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

建立@BindView註解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindView {
    @IdRes int value(); // @IdRes 只能傳 id 資源
}

使用@BindView註解

MainActivity 裏使用 @BindView 註解,如:

public class MainActivity extends AppCompatActivity {

    // 使用註解
    @BindView(R.id.text_view)
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        // 初始化 InjectView,完成自動 findViewById 功能
        InjectView.init(this);
        // 測試 R.id.text_view 是否自動賦值給 textView
        textView.setText("經過 @BindView 註解自動完成 findViewById");
    }
}

運行結果,如圖:

是否是很簡單,一個類就完成了自動 findViewById 的功能。

動態代理

在瞭解動態代理以前,咱們先來回顧下靜態代理。

靜態代理

代理模式給某一個對象提供一個代理對象,並由代理對象控制對原對象的引用,如,咱們生活中常見的中介。

代理模式通常會有3個角色,如圖:

  • 抽象角色:指代理角色和真實角色對外提供的公共方法,通常爲一個接口
  • 真實角色:須要實現抽象角色接口,定義了真實角色所要實現的業務邏輯,以便供代理角色調用
  • 代理角色:須要實現抽象角色接口,是真實角色的代理,經過真實角色的業務邏輯方法來實現抽象方法,並能夠附加本身的操做

爲何要使用代理模式

  • 能夠間接訪問對象,防止直接訪問對象來的沒必要要複雜性
  • 經過代理對象對訪問進行控制

靜態代理案例

場景以下:

小明能夠在某網站上購買國內的東西,可是,不能買海外的東西,因而,他找了海外代購幫他買東西。

如何用代碼描述呢?根據代理模式的3個角色,咱們分別定義1個接口2個類,如:OrderService 接口(抽象角色)、ImplJapanOrderService 類(真實角色)、ProxyJapanOrder 類(代理角色)

OrderService 接口(抽象角色),代碼以下:

public interface OrderService {
    int saveOrder();
}

ImplJapanOrderService 類(真實角色),代碼以下:

// 實現抽象角色接口
public class ImplJapanOrderService implements OrderService {
    @Override
    public int saveOrder() {
        System.out.println("下單成功,訂單號爲:888888");
        return 888888;
    }
}

ProxyJapanOrder 類(代理角色),代碼以下:

// 實現抽象角色接口
public class ProxyJapanOrder implements OrderService {

    private OrderService orderService; // 持有真實角色

    public OrderService getOrderService() {
        return orderService;
    }

    public void setOrderService(OrderService orderService) {
        this.orderService = orderService;
    }

    @Override
    public int saveOrder() {
        System.out.print("日本代購訂單,");
        return orderService.saveOrder(); // 調用真實角色的行爲方法
    }
}

在建立一個 Client 類來測試咱們的代碼,以下:

public class Client {

    public static void main(String[] args) {
        // 日本代購訂單
        OrderService orderJapan = new ImplJapanOrderService();
        ProxyJapanOrder proxyJapanOrder = new ProxyJapanOrder();
        proxyJapanOrder.setOrderService(orderJapan);
        proxyJapanOrder.saveOrder();
    }
}

運行結果,以下:

日本代購訂單,下單成功,訂單號爲:888888

BUILD SUCCESSFUL in 1s

若是,須要購買韓國的東西,須要新增一個 ImplKoreaOrderService 類(韓國服務商) 和 ProxyKoreaOrder 類(韓國代理),如還須要購買其餘國家的東西,須要新增不一樣的類,則會出現靜態代理對象量多、代碼量大,從而致使代碼複雜,可維護性差的問題,如是,咱們須要使用動態代理。

動態代理

動態代理是在運行時才建立代理類和其實例,所以,咱們能夠傳不一樣的真實角色,實現一個代理類完成多個真實角色的行爲方法,固然,其效率比靜態代理低。那麼如何實現動態代理呢,JDK已爲咱們提供了 Proxy 類 和 InvocationHandler 接口來完成這件事情。

咱們來建立一個 ProxyDynamicOrder 類(動態代理類),代碼以下:

public class ProxyDynamicOrder implements InvocationHandler {

    private Object orderService; // 持有真實角色

    public Object getOrderService() {
        return orderService;
    }

    public void setOrderService(Object orderService) {
        this.orderService = orderService;
    }
    // 經過 Proxy 動態建立真實角色
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(
                orderService.getClass().getClassLoader(),
                orderService.getClass().getInterfaces(),
                this
                );
    }

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        return method.invoke(orderService, objects); // 經過反射執行真實角色的行爲方法
    }
}

在來看看,Client 類裏如何調用,代碼以下:

public class Client {

    public static void main(String[] args) {

        // 靜態代理模式
        // 國內訂單
        OrderService order = new ImplOrderService();
        order.saveOrder();
        // 日本代購訂單
        OrderService orderJapan = new ImplJapanOrderService();
        ProxyJapanOrder proxyJapanOrder = new ProxyJapanOrder();
        proxyJapanOrder.setOrderService(orderJapan);
        proxyJapanOrder.saveOrder();
        // 韓國代購訂單
        OrderService orderKorea = new ImplKoreaOrderService();
        ProxyKoreaOrder proxyKoreaOrder = new ProxyKoreaOrder();
        proxyKoreaOrder.setOrderService(orderKorea);
        proxyKoreaOrder.saveOrder();

        // 動態代理模式
        // 國內訂單
        ProxyDynamicOrder proxyDynamicOrder = new ProxyDynamicOrder();
        OrderService orderService = new ImplOrderService();
        proxyDynamicOrder.setOrderService(orderService);
        OrderService orderService1 = (OrderService) proxyDynamicOrder.getProxyInstance();
        orderService1.saveOrder();

        // 日本代購訂單
        OrderService japanOrderService = new ImplJapanOrderService();
        proxyDynamicOrder.setOrderService(japanOrderService);
        OrderService japanOrderService1 = (OrderService) proxyDynamicOrder.getProxyInstance();
        japanOrderService1.saveOrder();

        // 韓國代購訂單
        OrderService koreaOrderService = new ImplKoreaOrderService();
        proxyDynamicOrder.setOrderService(koreaOrderService);
        OrderService koreaOrderService1 = (OrderService) proxyDynamicOrder.getProxyInstance();
        koreaOrderService1.saveOrder();

        // 生成動態代理生成的class文件
        //ProxyUtil.generateClassFile(koreaOrderService.getClass(), koreaOrderService1.getClass().getSimpleName());

    }
}

運行結果,以下:

下單成功,訂單號爲:666666
日本代購訂單,下單成功,訂單號爲:888888
韓國代購訂單,下單成功,訂單號爲:666888
下單成功,訂單號爲:666666
下單成功,訂單號爲:888888
下單成功,訂單號爲:666888

BUILD SUCCESSFUL in 1s

只須要一個 ProxyDynamicOrder 代理類便可完成 ImplOrderServiceImplJapanOrderServiceImplKoreaOrderService 真實角色提供的服務。

動態代理原理

咱們在 proxyDynamicOrder.getProxyInstance() 代碼上打個斷點,經過調試模式發現,如圖:

代理類的名字是 $Proxy0@507,爲何是這個名字,咱們在編譯後的目錄裏也找不到 $Proxy0@507 類文件,如圖:

咱們經過查看 Proxy.newProxyInstance 方法源碼,可知,如:

@CallerSensitive
public static Object newProxyInstance(ClassLoader var0, Class<?>[] var1, InvocationHandler var2) throws IllegalArgumentException {
    Objects.requireNonNull(var2);
    Class[] var3 = (Class[])var1.clone();
    SecurityManager var4 = System.getSecurityManager();
    if (var4 != null) {
        checkProxyAccess(Reflection.getCallerClass(), var0, var3);
    }
    // 獲取代理類的 class 對象
    Class var5 = getProxyClass0(var0, var3);

    try {
        if (var4 != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), var5);
        }
        // 獲取代理類的構造器
        final Constructor var6 = var5.getConstructor(constructorParams);
        if (!Modifier.isPublic(var5.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    var6.setAccessible(true);
                    return null;
                }
            });
        }
        // 建立代理類的示例
        return var6.newInstance(var2);
    } catch (InstantiationException | IllegalAccessException var8) {
        throw new InternalError(var8.toString(), var8);
    } catch (InvocationTargetException var9) {
        Throwable var7 = var9.getCause();
        if (var7 instanceof RuntimeException) {
            throw (RuntimeException)var7;
        } else {
            throw new InternalError(var7.toString(), var7);
        }
    } catch (NoSuchMethodException var10) {
        throw new InternalError(var10.toString(), var10);
    }
}

而後,跟進 getProxyClass0(var0, var3) 看看是如何獲取代理類的 class 對象的,點擊進入,以下:

private static Class<?> getProxyClass0(ClassLoader var0, Class<?>... var1) {
    if (var1.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    } else {
        // 緩存了代理類的 class 對象
        return (Class)proxyClassCache.get(var0, var1);
    }
}

而後,咱們來看看這個 var1 是個什麼東西,咱們往上找了找,果真發現,以下:

// var1 就是咱們實現的 InvocationHandler 接口
protected Proxy(InvocationHandler var1) {
    Objects.requireNonNull(var1);
    this.h = var1;
}

而後,咱們點進 proxyClassCache.get(var0, var1) 方法,如圖:

使用關鍵代碼 this.subKeyFactory.apply(var1, var2) 去獲取咱們的代理類的 class 對象,咱們進入 apply 實現類 ProxyClassFactory,如:

public Class<?> apply(ClassLoader var1, Class<?>[] var2) {
    IdentityHashMap var3 = new IdentityHashMap(var2.length);
    Class[] var4 = var2;
    int var5 = var2.length;

    ...

    if (var16 == null) {
        var16 = "com.sun.proxy.";
    }

    long var19 = nextUniqueNumber.getAndIncrement();
    // 生成代理類的類名
    String var23 = var16 + "$Proxy" + var19;
    // 生成代理類的字節碼
    byte[] var22 = ProxyGenerator.generateProxyClass(var23, var2, var17);

    try {
        // 生成代理類的 class 對象
        return Proxy.defineClass0(var1, var23, var22, 0, var22.length);
    } catch (ClassFormatError var14) {
        throw new IllegalArgumentException(var14.toString());
    }
}

而後,咱們點進 Proxy.defineClass0 方法,以下:

private static native Class<?> defineClass0(ClassLoader var0, String var1, byte[] var2, int var3, int var4);

是一個 native 方法,因此涉及到 C 或 C++ ,咱們就不日後追蹤。

那麼,代理的 Class 文件到底存在哪兒呢,由一個類的生命週期,如圖:

代理的 Class 文件經過反射存在內存中,因此咱們能夠經過 byte[] 寫入文件,咱們新建一個工具類來把內存中的 class 字節碼寫入文件,如:

public class ProxyUtil {

    public static void generateClassFile(Class aClass, String proxyName) {

        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName,
                new Class[]{aClass}
        );
        String path = aClass.getResource(".").getPath();
        System.out.println(path);
        FileOutputStream outputStream = null;

        try {
            outputStream = new FileOutputStream(path + proxyName + ".class");
            outputStream.write(proxyClassFile);
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

經過輸出的 path 路徑,找到文件,如:

/Users/lishaoying/Documents/APP/Android/practice/annotation_reflect/anRePrDemo/proxyDemo/build/classes/java/main/net/lishaoy/proxydemo/service/impl/

文件代碼,以下:

// 繼承了 Proxy 實現了 ImplKoreaOrderService 接口
public final class $Proxy0 extends Proxy implements ImplKoreaOrderService {

    // 生成了各類方法
    private static Method m1;
    private static Method m8;
    private static Method m3;
    private static Method m2;
    private static Method m5;
    private static Method m4;
    private static Method m7;
    private static Method m9;
    private static Method m0;
    private static Method m6;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    ...

    // 生成了 真實角色的 saveOrder 方法
    public final int saveOrder() throws  {
        try {
            // h 是什?,點進去發現就是咱們 傳入的 InvocationHandler 接口
            // m3 是什麼? 下面 static 代碼塊,就是咱們的 saveOrder 方法
            return (Integer)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    ...

    public final Class getClass() throws  {
        try {
            return (Class)super.h.invoke(this, m7, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    ...

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m8 = Class.forName("net.lishaoy.proxydemo.service.impl.ImplKoreaOrderService").getMethod("notify");
            m3 = Class.forName("net.lishaoy.proxydemo.service.impl.ImplKoreaOrderService").getMethod("saveOrder");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m5 = Class.forName("net.lishaoy.proxydemo.service.impl.ImplKoreaOrderService").getMethod("wait", Long.TYPE);
            m4 = Class.forName("net.lishaoy.proxydemo.service.impl.ImplKoreaOrderService").getMethod("wait", Long.TYPE, Integer.TYPE);
            m7 = Class.forName("net.lishaoy.proxydemo.service.impl.ImplKoreaOrderService").getMethod("getClass");
            m9 = Class.forName("net.lishaoy.proxydemo.service.impl.ImplKoreaOrderService").getMethod("notifyAll");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m6 = Class.forName("net.lishaoy.proxydemo.service.impl.ImplKoreaOrderService").getMethod("wait");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

使用註解、反射、動態代理完成簡單的Retrofit

因爲文章篇幅已經很長,且使用註解、反射、動態代理完成簡單的 Retrofit 的案例代碼過多,因此就再也不這裏展現,感興趣的小夥伴能夠去 GitHub 查看源碼。

最後附上博客和GitHub地址,以下:

博客地址:https://h.lishaoy.net
GitHub地址:https://github.com/persilee

相關文章
相關標籤/搜索