反射,註解,動態代理,依賴注入控制反轉

稀飯_發表於2018-06-07

一.反射呼叫方法,獲取成員變數值

假如我們有這樣一個類:

public class Hello {

    public String name = "張三";

    private int age = 43;

    public String getSex( String aaa) {

        return aaa;
    }

    private String getClassName() {

        return "還在九年義務教育";
    }

}
複製程式碼


要求不去建立物件去呼叫裡邊的方法和獲取裡邊的值,我們可以通過反射去獲取成員變數裡邊的值和呼叫裡邊的方法:

1.1 呼叫public修飾的方法:

try {
    //獲取類的位元組碼
    Class<Hello> clz = Hello.class;
    //獲取公共方法物件
    Method mt = clz.getMethod("getSex", String.class);
    //繫結呼叫方法,獲取返回值
    String invoke = (String) mt.invoke(clz.newInstance(), "eeeeee");
    //列印返回值
    Log.e("invoke", invoke);
} catch (Exception e) {
}複製程式碼

1.2 呼叫private修飾的方法:

try {
    //獲取類的位元組碼
    Class<Hello> clz = Hello.class;
    //獲取私有方法物件
    Method getClassName = clz.getDeclaredMethod("getClassName");
    //開啟修飾符限制
    getClassName.setAccessible(true);
    //呼叫方法
    String invoke = (String) getClassName.invoke(clz.newInstance());
    //列印返回值
    Log.e("invoke", invoke);
} catch (Exception e) {
    e.printStackTrace();
}複製程式碼

1.3 獲取public 修飾的成員變數:

try {
    //獲取類的位元組碼
    Class<Hello> clz = Hello.class;
    //獲取公共屬性物件
    Field name = clz.getField("name");
    //獲取公共屬性的名字
    String name1 = name.getName();
    //獲取公共屬性的值
    Object name2 = name.get(clz.newInstance());
    Log.e("rrrrrrrrre",name1+":"+name2);
} catch (Exception e) {
    e.printStackTrace();
} 複製程式碼

1.4 獲取private修飾的成員變數:

try {
    //獲取類的位元組碼
    Class<Hello> clz = Hello.class;
    //獲取私有屬性物件
    Field age = clz.getDeclaredField("age");
    //獲取私有屬性的名字
    String age1 = age.getName();
    //開啟許可權修飾符開關
    age.setAccessible(true);
    //獲取私有屬性的值
    int age2 =  age.getInt(clz.newInstance());
    //列印獲取到的值
    Log.e("rrrrrrrrre",age1+":"+age2);
} catch (Exception e) {
    e.printStackTrace();
} 複製程式碼

二. 註解的知識點

2.1.註解

就是 :使用 @interface 標識一下,它也是一種型別 ,同類和介面列舉一樣如:

public @interface TestAnnotation {


}複製程式碼

2.2.元註解

要想讓自定義註解管用,需要用的元註解 。元註解就是可以註解到自定義註解上的註解 ,元註解有一下:

  • @Retention
  • @Documented
  • @Target
  • @Inherited
  • @Repeatable

2.3 @Retention 解釋說明了自定義註解的保留期,取值有:

  •  RetentionPolicy.SOURCE 註解只在原始碼階段保留,在編譯器進行編譯時它將被丟棄忽視。 
  •  RetentionPolicy.CLASS 註解只被保留到編譯進行的時候,它並不會被載入到 JVM 中。
  •  RetentionPolicy.RUNTIME 註解可以保留到程式執行的時候,它會被載入進入到 JVM 中,所以在程式執行時可以獲取到它們。

2.4 @Documented 文件,我也不知道幹什麼用的

2.5 @Target 解釋說明了自定義註解的作用範圍,有下面的取值:

  •  ElementType.ANNOTATION_TYPE 可以給一個註解進行註解
  • ElementType.CONSTRUCTOR 可以給構造方法進行註解  
  • ElementType.FIELD 可以給屬性進行註解
  •  ElementType.LOCAL_VARIABLE 可以給區域性變數進行註解
  •  ElementType.METHOD 可以給方法進行註解
  • ElementType.PACKAGE 可以給一個包進行註解  
  • ElementType.PARAMETER 可以給一個方法內的引數進行註解
  •  ElementType.TYPE 可以給一個型別進行註解,比如類、介面、列舉

2.6@Inherited Inherited 

是繼承的意思,但是它並不是說註解本身可以繼承而是說如果一個超類被 @Inherited 註解過的註解進行註解的話,那麼如果它的子類沒有被任何註解應用的話,那麼這個子類就繼承了超類的註解。

2.7@Repeatable 重複的意思

2.8 註解只有屬性,沒有方法,寫法: int id() default -1; 名字是:id,型別是int ,預設值是-1 ,如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {

    public int id() default -1;

    public String msg() default "Hi";

}複製程式碼

2.9 注意:

  •  如果一個註解內僅僅只有一個名字為 value 的屬性時,應用這個註解時可以直接接屬性值填寫到括號內 
  • 註解沒有任何屬性,應用這個註解的時候,括號都可以省略


三 註解的獲取

假如註解要求執行時也可以獲取,那麼我們怎麼獲取呢? 通過反射,介紹Class裡邊的一些方法:

如有類Test 和自定義註解TestAnnotation

//是否有被TestAnnotation註解
boolean annotation = Test.class.isAnnotationPresent(TestAnnotation.class);
if (annotation) {
    //獲取註解物件
    TestAnnotation annotation1 = Test.class.getAnnotation(TestAnnotation.class);
    //獲取被註解的值
    Log.e("rrrrrr", "" + annotation1.id() + "=========" + annotation1.msg());
}複製程式碼

四.反射和註解有什麼用

仿照ButterKnife寫一個類似的功能,但是原理不一樣,主要是為了理解反射我註解,當然反射和註解還有很多很大的用處,我能力有限,理解不了那麼深,開始貼程式碼:

佈局:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
    android:layout_height="match_parent">
<TextView
    android:layout_width="match_parent"
    android:id="@+id/tv"
    android:text="aaaa"
    android:layout_height="wrap_content" />
</android.support.constraint.ConstraintLayout>複製程式碼

程式:

@BindRes(R.layout.activity_test)
public class TestAnnotation extends AppCompatActivity {
    @Bind(R.id.tv)
    TextView mTextView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Inject.inJect(this);
        mTextView.setText("XiFanYin");
    }
    @BindClick(R.id.tv)
    public void onClick2(View view) {
        if (view.getId() == R.id.tv) {
            Toast.makeText(this, "點選", Toast.LENGTH_SHORT).show();
        }
    }
}複製程式碼

自定義註解:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bind {
    int value();

}複製程式碼
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindClick {
    int[] value();

}複製程式碼
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindRes {
    int value();
}
複製程式碼


然後就是關鍵的一個類,通過反射去找控制元件賦值,設定點選事件:

public class Inject {
    public static void inJect(AppCompatActivity activity) {

        try {
            bindRes(activity);//繫結佈局
            bindView(activity);//查詢控制元件
            bindClick(activity);//查詢控制元件

        } catch (Exception e) {

            e.printStackTrace();
        }
    }

    private static void bindClick(AppCompatActivity activity) {
        Class<? extends AppCompatActivity> clz = activity.getClass();
        Method[] methods = clz.getMethods();//得到所有方法
        for (Method method : methods) {
            if (method.isAnnotationPresent(BindClick.class)) {//方法下是否有BindClick註解
                BindClick annotation = method.getAnnotation(BindClick.class);
                int[] value = annotation.value();
                for (int id : value) {
                    View view = activity.findViewById(id);
                    view.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            method.setAccessible(true);
                            try {
                                method.invoke(activity, view);
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();

                            } catch (InvocationTargetException e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }


            }
        }

    }

    private static void bindRes(AppCompatActivity activity) {
        Class<? extends AppCompatActivity> clz = activity.getClass();
        BindRes annotation = clz.getAnnotation(BindRes.class);
        if (annotation != null) {
            int value = annotation.value();
            activity.setContentView(value);
        }
    }

    public static void bindView(AppCompatActivity activity) throws IllegalAccessException {
        Class<? extends AppCompatActivity> clz = activity.getClass();
        Field[] fields = clz.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {

            if (fields[i].isAnnotationPresent(Bind.class)) {
                Bind bind = fields[i].getAnnotation(Bind.class);
                int value = bind.value();
                View view = activity.findViewById(value);
                fields[i].setAccessible(true);

                fields[i].set(activity, view);

            } else {
                System.out.println("沒有");
            }
//            System.out.println(fields[i]);
        }
    }
}
複製程式碼

就實現了類似ButterKnife的功能。

四.動態代理

作用:在不修改被代理物件的原始碼上,進行功能的增強。

定義一個可以賣東西營業執照介面

public interface Sell {

    void maidongxi();

}複製程式碼

有一個人想買紅旗渠煙,就要實現這個營業執照

public class hongqiqu implements Sell {
    @Override
    public void maidongxi() {
        Log.e("rrrrrrrrrr", "我能賣紅旗渠");
    }
}複製程式碼

過了一段時間,他想掙的更多,就像去賣酒。然後我們需要修改hongqiqu類的程式碼,如果對於專案而言,要麼去繼承,要麼去使用包裝者模式去擴充套件,很少修改本來的類,今天我們使用動態代理去實現:

public class GuitaiA implements InvocationHandler {

    private Object pingpai;

    public GuitaiA(Object pingpai) {
        this.pingpai = pingpai;
    }

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        Log.e("rrrrrrr","我能賣酒了");
        method.invoke(pingpai, objects);
        return null;

    }
}複製程式碼

看著這個模式有點像包裝者模式,只不過加了反射呼叫,簡單的修改都能讓他不再去關係去呼叫什麼方法名字是什麼。

然後載主介面中,

//紅旗渠物件
hongqiqu hongqiqu = new hongqiqu();
//傳遞到裡邊
InvocationHandler jingxiao = new GuitaiA(hongqiqu);
//獲取對臺代理介面
Sell dynamicProxy= (Sell) Proxy.newProxyInstance(hongqiqu.class.getClassLoader()
        , hongqiqu.class.getInterfaces()
        , jingxiao);
dynamicProxy.maidongxi();複製程式碼

發現沒有修改hongqiqu類中的程式碼,他就可以即賣紅旗渠,又可以賣酒

這就是動態代理,把豐富的功能抽離出來,傳入誰,我就去豐富擁有我這個功能!完美解耦~

五,依賴注入和控制反轉

假如你要出遠門,你可以選擇火車或者汽車,按照常規編碼:

如果選擇火車,程式碼應該這樣寫

public class Person {

    private HotCar car;

    public Person() {
        car = new HotCar();
    }


    public void chumen() {
        car.drive();

    }
}複製程式碼

如果選擇汽車,程式碼修改成這樣:

public class Person {
//    private HotCar hotCar;
    private Car car;

    public Person() {
        car = new Car();
//        hotCar = new HotCar();
    }


    public void chumen() {
        car.drive();
//        hotCar.drive();
    }
}複製程式碼

每次選擇不同的交通工具,都要修改,能不能不修改?可以這樣寫:

5.1 抽象介面:

public interface Driveable {
    //駕駛
    void drive();
}
複製程式碼

5.2.人傳入介面

public class Person {


    private Driveable driveable;

    public Person(Driveable driveable) {
        this.driveable = driveable;
    }

    public void chumen() {

        driveable.drive();
    }
}
複製程式碼

5.3 呼叫

Car car = new Car();
HotCar hotCar = new HotCar();
Person p = new Person(hotCar);
p.chumen();複製程式碼

這裡傳入什麼物件去使用什麼方法,通過介面去解耦,而不用每次都去修改person類,讓我們更專注與原則什麼樣的交通工具,這個傳遞的物件就叫依賴注入,控制權同時也進行了轉換,不再讓person去控制,而是我傳入什麼,你只能用什麼,這就是依賴注入和控制反轉。


相關文章