Java反射,註解,以及動態代理

Java反射,註解,以及動態代理

最近在準備實習面試,被學長問到了Java反射,註解和動態代理的內容,發現有點本身有點懵,這幾天查了不少資料,就來講下本身的理解吧【若有錯誤,望指正】html

Java反射

首先,咱們得弄清一個,什麼是反射(Reflection)。簡單的來講,反射就是讓咱們在程序運行的時候可以查看到類的信息,獲取並調用類的任意方法和屬性。java

在Java運行時,系統會將全部的對象維護一個被稱爲運行是的類型標識,而後這個信息跟蹤這每一個對象所屬的類,咱們能夠和根據Java專門的類訪問這些信息,這個類就是Class【實際上Class對象表示的是一個類型,它不必定是類,多是基本數據類型,好比int】。面試

Class獲取方法

  1. 經過getClass()獲取數組

    Student stu = new Student;
    Class c =  stu.getClass();
    //若是這個類在包裏面,則會將包名也打印出來
    // getSimpleName只得到類名
    System.out.println(c.getName());
  2. 使用forName獲取
    一樣,咱們也可使用靜態方法forName得到Class對象。例如:dom

    String className= "java.util.Random";
    Class c2 = Class.forName(className);

    固然,className必須爲接口或者類名才能成功。ide

  3. 直接獲取this

    Class c3 = Student.class;

由Class獲得對象

  1. 使用Class對象的newInstance()方法來建立Class對象spa

    Class c3 = Test.class;
    Object o = c3.newInstance();

    其中newInstance()會根據類的默認構造器【無參構造器】建立新的對象,若是沒有默認的構造器,就會報錯。假如咱們的構造器裏面有參數怎麼辦,這時候咱們就須要使用java.lang.reflect.Constructor中的newInstance()方法了。代理

  2. 使用Constructor類中的newInstance()code

    // getConstructor裏面是構造參數的形參內容
    Constructor constructor = c3.getConstructor(String.class);
    Object o = constructor.newInstance("你好");

java反射中最重要的內容——檢查類的結構

在Java的java.lang.reflect中有三個類:Field、Method、Constructor分別來描述類的域【也就是變量】,方法和構造器。

  1. Field的獲取以及方法

    Class textClass = Test.class;
    // getDeclaredFields()得到這個類的所有域
    // getField()得到公有域以及其父類的公有域
    Field[] fields = textClass.getDeclaredFields();

    簡單的來講,經過Field能夠得到:

    變量的權限——getModifiers(),返回int,而後經過Modifier.toString(int)得到訪問權限

    得到變量的類型——getType()

    變量的名字——getName()

  2. Method的獲取以及方法

    Class textClass = Test.class;
    // 一樣可使用getMethods()和getDeclaredMethods()返回接口和類的方法
    Method[] methods = textClass.getMethods();

    經過Method能夠獲取:

    方法的權限——getgetModifiers()

    方法的返回值類型——getReturnType(),方法返回類型爲Class,而後你懂得。

    方法的全部參數——Parameter[] parameters = method.getParameters();

    方法的執行——invoke()。在獲取一個方法後,咱們可使用invoke()來調用這個方法。

    Object invoke(Object obj,Object...args),obj爲實例化後的對象【對於靜態方法能夠被設置null】,args爲方法調用的參數

    例如,

    public class Test {
    
        public void say(String msg){
            System.out.println(msg);
        }
        public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
    
            Class c = Test.class;
            // 返回惟一的方法,第一個參數是方法的名字,第二個是方法參數的類型
            Method method = c.getMethod("say", String.class);
            Object o = c.newInstance();
            method.invoke(o,"你好");
        }
    }
  3. Constructor的獲取以及方法

    Class textClass = Test.class;
    // 一樣getDeclaredConstructors()和getConstructors()
    Constructor[] constructors = aClass.getConstructors();

    方法的的使用和Method差很少,可是它沒有getReturnType()方法。

這些方法我只是簡單的介紹了一下,詳細信息能夠參考API。

神奇的Java註解

Java註解能夠很簡單的說,就是爲方法或者其餘數據提供描述的東西。

它的本質就是一個接口,一個繼承了Annotation的接口。

  1. 基本java註解的類型
    【元註解】:也就是在自定義一個註解時,能夠註解在註解上面,有如下幾個元註解——>

    • @Target:註解的做用目標,用來指明註解能夠做用的目標是誰,例如類,方法或者字段屬性,裏面的value【爲一個ElementType數組】能夠指明值以下:

      ElementType.TYPE:容許被修飾的註解做用在類、接口和枚舉上

      ElementType.FIELD:容許做用在屬性字段上

      ElementType.METHOD:容許做用在方法上

      ElementType.PARAMETER:容許做用在方法參數上

      ElementType.CONSTRUCTOR:容許做用在構造器上

      ElementType.LOCAL_VARIABLE:容許做用在本地局部變量上

      ElementType.ANNOTATION_TYPE:容許做用在註解上

      ElementType.PACKAGE:容許做用在包上

    • @Retention:註解的生命週期,裏面的value【枚舉類型】能夠指明值以下:

      RetentionPolicy.SOURCE:當前註解編譯期可見,不會寫入 class 文件

      RetentionPolicy.CLASS:類加載階段丟棄,會寫入 class 文件

      RetentionPolicy.RUNTIME:永久保存,能夠反射獲取
      - @Documented:註解是否應當被包含在 JavaDoc 文檔中
      - @Inherited:是否容許子類繼承該註解
      - @Repeatable:重複註解,容許這個註解在某個方法和其餘數據上面重複使用

    【Java內置三大註解】:除了上述元註解,Java還內置了另外三種註解——>

    • @Override:子類重寫父類的方法時,會使用該註解。用於檢查父類是否包含該註解
    • @Deprecated:當某一方法和字段不推薦使用時,使用該註解標註。
    • @SuppressWarnings:壓制Java的警告
  2. Java註解的自定義以及實現

    Java註解的自定義以下

    @Target(value = {ElementType.METHOD,ElementType.TYPE}) // 註解的做用地方
    @Retention(value = RetentionPolicy.RUNTIME)  // 註解的生命週期
    public @interface TestAnnotation {
        String name() default "這是個類";
        int time();
    }

    那麼咱們該若是如何使用註解發揮做用呢?咱們能夠想一想,若是咱們可以得到註解的信息,那麼咱們是否是就能夠根據註解的信息來對方法作適當的調整。這時候,固然是大名鼎鼎的反射出馬了。

    • java.lang.Package.getAnnotation(Class<A> annotationClass) 得到這個指令類型的註解。

    使用以下:

    @TestAnnotation(time = 0)
    public class Test {
    
        @TestAnnotation(name = "這是個方法",time = 1)
        public void say(){
        }
        public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
            // 得到類上面的註解
            TestAnnotation classAnnotation = Test.class.getAnnotation(TestAnnotation.class);
            System.out.println("類的名字爲:"+classAnnotation.name()+"------類的時間是"+classAnnotation.time());
            Method method = Test.class.getMethod("say");
    
            // 得到方法上面的註解
            TestAnnotation methodAnnotation = method.getAnnotation(TestAnnotation.class);
            System.out.println("方法的名字是:"+methodAnnotation.name()+"------方法的時間是"+methodAnnotation.time());
        }
    }
    
    // 輸出:
    // 類的名字爲:這是個類------類的時間是0
    // 方法的名字是:這是個方法------方法的時間是1

    如今咱們知道如何進行自定義註解的使用了,那麼咱們怎麼可以根據註釋內容的不一樣去改變方法的執行呢?這時候,咱們咱們就可使用invoke()方法了。

    舉個最簡單的栗子:

    @TestAnnotation(name = "你好")
        public void say(String msg){
            System.out.println("信息是:"+msg);
        }
        public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
    
            Method method = Test.class.getMethod("say",String.class);
            // 得到方法上面的註解
            TestAnnotation methodAnnotation = method.getAnnotation(TestAnnotation.class);
            // 執行方法
            method.invoke(Test.class.newInstance(),methodAnnotation.name());
        }
        // 輸出結果:
        // 信息是:你好

代理

代理就是給某個對象提供一個代理對象,並由代理對象控制對於原對象的訪問,即客戶不直接操控原對象,而是經過代理對象間接地操控原對象。
代理分爲:

  • 靜態代理:代理類是在編譯時就已經實現好了,成爲了一個class文件
  • 動態代理:是在程序運行時動態地生成類字節碼,而後加載到JVM中

有幾個概念:

  1. 抽象角色:接口類
  2. 實現角色:實現類
  3. 代理角色:代理實現的類,最終使用的對象

靜態代理

在說動態代理以前,咱們先說一下靜態代理,靜態代理很簡單,就是工廠模式。

那麼就讓咱們來實現一下靜態代理吧

抽象角色:接口類

public interface TestService {
    void say();
    void play();
}

實現角色:實現類

public class TestServiceImpl implements TestService {

    @Override
    public void say() {
        System.out.println("說話乎");
    }

    @Override
    public void play() {
        System.out.println("浪的飛起");
    }
}

代理類

public class Test implements TestService{

    private TestService testService;

    public Test(TestService testService) {
        this.testService = testService;
    }

    @Override
    public void say() {
        System.out.println("開始說話");
        testService.say();
        System.out.println("結束說話");
    }

    @Override
    public void play() {
        System.out.println("開始浪");
        testService.play();
        System.out.println("是個狼人");
    }

    public static void main(String[] args) {
        TestServiceImpl testImpl = new TestServiceImpl();
        Test test = new Test(testImpl);
        test.play();
        test.say();
    }
}

在這裏面,咱們能夠看到,從外表看起來say()play()方法都是由test這個代理來完成的,但實際上,真正的執行者是TestServiceImpl來完成的,test只是在執行的時候加了一些事務邏輯。

既然有了靜態代理,爲何咱們還須要動態代理呢?從代碼中能夠看出,代理類和實現類是一一對應的,若是咱們有N個實現類,都要在方法執行前加同樣的邏輯,那麼咱們不得不建立N個代理類。這時候,咱們就須要使用動態代理了。

動態代理

本次動態代理是針對JDK動態代理進行探討。

正如前面所說,若是咱們要在不少類使用同一種邏輯時,會心態爆炸,那麼咱們怎麼去解決這個問題呢,這時候,咱們能夠想想反射。

在使用的動態代理的過程當中,有兩個關鍵的東東,一個是InvocationHandler接口,一個是Proxy類。

  • InvocationHandler

每個動態代理類都必需要實現InvocationHandler這個接口,而且每一個代理類的實例都關聯到了一個handler,當咱們經過代理對象調用一個方法的時候,這個方法的調用就會被轉發爲由InvocationHandler這個接口的 invoke 方法來進行調用。

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

proxy:  指代咱們所代理的那個真實對象,也就是實現類

method:  指代的是咱們所要調用真實對象的某個方法的Method對象

args:  指代的是調用真實對象某個方法時接受的參數

  • Proxy

Proxy這個類的做用就是用來動態建立一個代理對象的類

其中咱們使用最可能是newProxyInstance()去建立代理類

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

loader:一個ClassLoader對象,定義了由哪一個ClassLoader對象來對生成的代理對象進行加載

interfaces:一個Interface對象的數組,表示的是我將要給我須要代理的對象提供一組什麼接口,若是我提供了一組接口給它,那麼這個代理對象就宣稱實現了該接口(多態),這樣我就能調用這組接口中的方法了

h:一個InvocationHandler對象,表示的是當我這個動態代理對象在調用方法的時候,會關聯到哪個InvocationHandler對象上

建立一個代理類,實現方法調用前或後的邏輯

public class TestHandler implements InvocationHandler{

    // object爲實現類的對象
    private Object object;

    public TestHandler(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("開始方法執行");
        Object o = method.invoke(object,args);
        System.out.println("方法結束");
        return o;
    }
}

實例化代理類,並

public static void main(String[] args) {

    // 實現類
    TestService testService = new TestServiceImpl();

    // 裏面傳入要代理的實現類對象
    TestHandler testHandler = new TestHandler(testService);
    /** * testService.getClass().getClassLoader() 表明咱們使用這個類來加載咱們代理對象 * testService.getClass().getInterfaces() 表明咱們調用這些接口中的方法 * testHandler 將代理對象與testHandler關聯 */
    TestService service = (TestService) Proxy.newProxyInstance(testService.getClass().getClassLoader(),
            testService.getClass().getInterfaces(),testHandler);
    service.play();
    service.say();

反射,註解,以及動態代理就簡單地介紹完了,能夠這樣說反射是註解以及動態代理的基礎,註解的實現和動態代理都要靠反射發揮做用。

仍是多讀下書吧,面試實習是把殺豬刀

 


 

 

 

相關文章
相關標籤/搜索