#注解、反射及動(dòng)態(tài)代理

一、注解(Annotation)

1.什么是注解:

??注解可以說是注釋的更高級的一種,相當(dāng)于標(biāo)記,注解同樣不影響代碼的執(zhí)行,但是注解能夠用來創(chuàng)建文檔、跟蹤代碼中的依耐性、執(zhí)行基本編譯時(shí)的檢查。
??同時(shí)注解是一個(gè)接口,程序可以通過反射來獲取指定程序中元素的Annotation對象,然后通過該對象獲取注解中元數(shù)據(jù)信息信息。

即:==Annotation(注解)是Java提供的一種對元程序中元素關(guān)聯(lián)信息和元數(shù)據(jù)(metadata)的一種途徑和方法。==

[引入時(shí)間:jdk1.5之后]

標(biāo)記可以用在包、類、方法、字段、方法參數(shù)及局部變量上來關(guān)聯(lián)信息。這些信息被儲存在Annotation的“name = value”結(jié)構(gòu)對中。

注意:這些標(biāo)記和關(guān)聯(lián)的信息并不會(huì)影響程序中代碼的任何執(zhí)行,而至于可以用在反射的時(shí)候在運(yùn)行期間被訪問,那是只有通過特定的工具才能對Anntation類型中的數(shù)據(jù)訪問和處理。

2.注解分類

按照注解參數(shù)個(gè)數(shù)分類:

  1. 標(biāo)記注解:一個(gè)沒有成員定義的Annotation類型被稱為標(biāo)記注解。
  2. 單值注解。
  3. 完整注解

根據(jù)注解的使用方法和用途分類:

  1. JDK內(nèi)置系統(tǒng)注解。
  2. 元注解。
  3. 自定義注解。

系統(tǒng)內(nèi)置標(biāo)準(zhǔn)注解:
3個(gè)標(biāo)準(zhǔn)內(nèi)置注解,定義在java.lang中:

  • @Override:用于修飾此方法重寫父類的方法。
  • @Deprecated:用于修飾已經(jīng)過時(shí)的方法。
  • @SuppressWarning:用于禁止特定的編譯警告。

==@Override:限定重寫父類方法==

使用這個(gè)注解來標(biāo)記我們重寫的方法,能夠幫我們判斷是否是重寫,即方法名寫錯(cuò)的時(shí)候,編譯報(bào)錯(cuò)。

==@Deprecated:標(biāo)記已過時(shí)==

使用這個(gè)注解標(biāo)記某個(gè)方法,讓這個(gè)方法稱為過時(shí)的方法,當(dāng)使用時(shí)候,能夠提示使用者這個(gè)方法已經(jīng)過時(shí)了,不推薦使用。

==@SuppressWarning:抑制編譯器警告==

使用這個(gè)注解的時(shí)候,可以去除我們不想看到或者出現(xiàn)的警告。(目前不知道這個(gè)有什么用。)

抑制編譯器警告參數(shù):

  1. deprecation:使用了不贊成的類或方法時(shí)的警告。
  2. unchecked: 使用了未檢查的轉(zhuǎn)換時(shí)的警告,例如使用集合的時(shí)候沒有用泛型(Generics)來指定集合保存的類型。
  3. fallthrough:當(dāng)Switch程序塊直接通往下一種情況而沒有使用break時(shí)的警告。
  4. path:當(dāng)類路徑、源文件中有不存在的路徑時(shí)的警告。
  5. serial:在可序列化的類上缺少serialVersionUID定義時(shí)的警告。
  6. finally:任何finally句子不能正常完成時(shí)的警告。
  7. all:以上所有的警告。

3.自定義注解

注解的深入學(xué)習(xí),自定義注解。想要自定義注解,首先我們要了解元注解及相關(guān)自定義注解的語法。

①元注解(meta-annotation):
元注解的作用是注解其他注解(也就是我們自定義的注解包括系統(tǒng)內(nèi)置標(biāo)準(zhǔn)注解)。

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

==@Target==

這個(gè)元注解說明了Annotation所修飾的范圍:即注解可被用于package、types(類、接口、枚舉、Annotation類型)、類型成員(方法、構(gòu)造方法、成員變量、枚舉值)、方法參數(shù)、本地變量(循環(huán)變量、catch參數(shù)等)。
用于描述注解的使用范圍,這個(gè)范圍的取值(ElementType)有:

  • CONSTRUCTOR:用于描述構(gòu)造器
  • FIELD:用于描述屬性
  • LOCAL_VARIABLE:用于描述局部變量
  • METHOD:用于描述方法
  • PACKAGE:用于描述包
  • PARAMETER:用于描述參數(shù)
  • TYPE:用于描述類、接口(包括注解類型)和enum申明、

使用實(shí)例:

/**
 * 注解Table可用于注解類、接口(包括Annotation類型)或enum申明
 */
Target(ElementType.TYPE)
public @interface Table {
    /**
     * 數(shù)據(jù)表名稱注解,默認(rèn)值為類名稱
     */
    public abstract String tableName() default "className";
}

/**
 * 注解NoDBColumn僅可用于注解類的成員變量
 */
Target(ElementType.FIELD)
public @interface NoDBColumn {
}

==@Retention==

作用:表示在什么級別保存注解信息,用于描述注解的生命周期(即被描述的注解在什么范圍內(nèi)有效),取值(RetentionPoicy):

  • SOURCE:在源文件中有效
  • CLASS:在class文件中有效
  • RUNTIME:在運(yùn)行時(shí)有效

具體實(shí)例:

/**
 * Column注解的RetentionPolicy屬性值是RUNTIME,這樣注解處理器就可以通過
 * 反射,獲取到該注解的屬性值,從而做一些運(yùn)行時(shí)的邏輯處理
 */
@(Java高級)[感謝網(wǎng)上各個(gè)大神文章, 讓我們明悟?。?!]
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    public abstract String name() default "fileName";
    public abstract String setFuncName() default "setField";
    public abstract String getFuncName() default "getField";
    public abstract boolean defaultDBValue() default false;
}

==@Documented==

是一個(gè)標(biāo)記注解,沒有成員。用于描述其他類型的annotation應(yīng)該作為被標(biāo)注的程序成員的公共API,即可以被javadoc這樣的工具文檔化。

具體實(shí)例:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Column {
    public abstract String name() default "fileName";
    public abstract String setFuncName() default "setField";
    public abstract String getFuncName() default "getField";
    public abstract boolean defaultDBValue() default false;
}

==@Inherited==

??這也是一個(gè)標(biāo)記注解,@Inherited闡述了某個(gè)被標(biāo)注的類型是被繼承的。如果一個(gè)使用了@Inherited修飾的annotation類型被用于一個(gè)class,則這個(gè)annotation將被用于該class的子類。
??注意@Inherited annotation類型是被標(biāo)注過的class的子類所繼承。類并不從它所實(shí)現(xiàn)的接口繼承annotation,方法并不從它所重載的方法繼承annotation。
??當(dāng)@Inherited annotation類型標(biāo)注的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強(qiáng)了這種繼承性。如果我們使用java.lang.reflect去查詢一個(gè)@Inherited annotation類型的annotation時(shí),反射代碼檢查將展開工作:檢查class和其父類,直到發(fā)現(xiàn)指定的annotation類型被發(fā)現(xiàn),或者到達(dá)類繼承結(jié)構(gòu)的頂層。

具體實(shí)例:

@Inherited
public @interface Greeting {

    public enum FontColor {
        BULE, RED, GREEN
    };

    String name();

    FontColor fontColor() default FontColor.GREEN;
}

②自定義注解
自定義注解格式:
public @interface 注解名{定義體}

注意事項(xiàng):自定義注解時(shí),自動(dòng)繼承java.lang.Annotation接口 ,由編譯程序自動(dòng)完成其他細(xì)節(jié)。同時(shí),不能繼承其他接口或注解。

定義體中每一個(gè)方法實(shí)際上都是一個(gè)配置參數(shù),方法名=參數(shù)名返回值類型=參數(shù)的類型(返回值類型只能是基本類型、Class、Annotation、String、enum和以上所有類型的數(shù)組),可以通過default申明參數(shù)的默認(rèn)值。

參數(shù)設(shè)定:

  • 只能用public和默認(rèn)(default)這兩個(gè)訪問權(quán)限修飾符修飾。
  • 只有一個(gè)參數(shù)成員時(shí),最好將參數(shù)名稱設(shè)置為"value"。

具體實(shí)例:

/**
 * 水果名稱注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
    String value() default "";
}

    /**
     * 顏色屬性
     * @return
     */
    Color fruitColor() default Color.BULE;
}
//被注解的類
public class Apple {
    @FruitName("Apple")
    private String appleName;

    @FruitColor(fruitColor = FruitColor.Color.RED)
    private String appleColor;

    public void setAppleColor(String appleColor) {
        this.appleColor = appleColor;
    }

    public String getAppleColor() {
        return appleColor;
    }

    public void setAppleName(String appleName) {
        this.appleName = appleName;
    }

    public String getAppleName() {
        return appleName;
    }

    public void displayName() {
        System.out.println("水果的名字是:蘋果");
    }
}

==注意:==

注解元素必須要有確定的值,即要么在定義注解中設(shè)置默認(rèn)值,要么在使用注解時(shí)給定值,一般使用空字符串或0作為默認(rèn)值,但是很難看出這個(gè)元素是存在或缺失的的特征,所以我們在注解定義的時(shí)候,給元素賦上特殊的值,空字符串或-1,這個(gè)是一種習(xí)慣。


4.注解處理器

對于我們寫的注解,如果不能去讀取它的話,那還不如寫注釋呢!
所以,接下來我們要做的就是創(chuàng)建使用注解處理器。

①注解處理器類庫(Java.lang.reflect.AnnotatedElement)
我們知道Annotation接口是所有Annotation類型的父接口,在java.lang.reflect包下有一個(gè)AnnotatedElement接口(它是所有程序元素的父接口),它有一些實(shí)現(xiàn)類使我們需要的:

  • Class :類的一些定義
  • Constructor :類的構(gòu)造器定義
  • Field :類的成員變量定義
  • Method :類的方法定義
  • Packet :包的一些定義

在java.lang.reflect包下有很多工具,是在我們反射的時(shí)候需要用的。

程序通過反射獲取了某個(gè)類的AnnotatedElement對象之后,程序就可以調(diào)用該對象的4個(gè)方法來訪問Annotation信息:

  • T getAnnotation(Class annotationClass):返回該程序元素上存在的、指定類型的注解,如果該類型注解不存在,則返回null。
  • Annotation[] getAnnotations():返回該程序元素上所有存在的注解。
  • boolean is AnnotationPresent(Class< ?extends Annotation> annotationClass):判斷該程序元素上是否包含指定類型的注解,存在返回true,不存在返回false。
  • **Annotation[] getDeclaredAnnotaions(): **返回直接存在于此元素上的所有注解。(忽略繼承過來的注解)如果沒有直接存在于次元素上的注解,則返回一個(gè)長度為0的數(shù)組。

下面是一個(gè)簡單的例子:

==自定義注解==

/**
 * 水果名稱注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
    String value() default "";
}

/**
*   水果顏色注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
    /**
     * 顏色枚舉
     */
    public enum Color{
      BULE , RED , GREEN
    };

    /**
     * 顏色屬性
     * @return
     */
    Color fruitColor() default Color.BULE;
}

/**
 * 水果供應(yīng)者注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
    /**
     * 供應(yīng)商編號
     */
    public int id() default -1;

    /**
     *
     * 供應(yīng)商名稱
     */
    public String name() default "";

    /**
     *
     * 供應(yīng)商地址
     */
    public String address() default "";
}

==自定義注解處理器==

/**
 * 注解處理器
 */
public class FruitInfoUtil {
    public static void getFruitInfo(Class<?> clazz){
        String strFruitName = "水果名稱:";
        String strFruitColor = "水果顏色:";
        String strFruitProvicer = "供應(yīng)商信息:";

        //返回直接存在于clazz上的所有注釋
        Field[] fields = clazz.getDeclaredFields();

        for(Field field : fields){
            if(field.isAnnotationPresent(FruitName.class)){
                FruitName fruitName = (FruitName) field.
                        getAnnotation(FruitName.class);
                strFruitName = strFruitName + fruitName.value();
                System.out.println(strFruitName);
            }else if(field.isAnnotationPresent(FruitColor.class)){
                FruitColor fruitColor = (FruitColor) field.
                        getAnnotation(FruitColor.class);
                strFruitColor = strFruitColor + fruitColor.fruitColor();
                System.out.println(strFruitColor);
            }else if(field.isAnnotationPresent(FruitProvider.class)){
                FruitProvider fruitProvider = (FruitProvider) field.
                        getAnnotation(FruitProvider.class);
                strFruitProvicer = "供應(yīng)商編號:" + fruitProvider.id()
                        + " 供應(yīng)商名稱:" + fruitProvider.name()
                        + " 供應(yīng)商地址:" + fruitProvider.address();
                System.out.println(strFruitProvicer);
            }


        }

    }
}

==測試類==

public class FruitRun {
    public static void main(String[] args) {
        FruitInfoUtil.getFruitInfo(Apple.class);
    }
}

運(yùn)行結(jié)果
水果名稱:Apple
水果顏色:RED
供應(yīng)商編號:9527 供應(yīng)商名稱:陜西紅富士集團(tuán) 供應(yīng)商地址:陜西省西安市延安路89號紅富士大廈

到此,我們注解就講完了,下面附上一張圖,便于我們整理知識點(diǎn)和鞏固知識點(diǎn)。
[圖片上傳失敗...(image-e731e1-1540044443552)]


二、反射

反射是Java動(dòng)態(tài)性之一。
何為動(dòng)態(tài)性,即動(dòng)態(tài)性語言:
??程序在運(yùn)行時(shí)可以改變其結(jié)構(gòu),新的函數(shù)可以引進(jìn),已有的函數(shù)可以被刪除等結(jié)構(gòu)上的變化。(如JavaScript、Ruby、Python等屬于動(dòng)態(tài)語言)

本質(zhì)上Java和C/C++一樣不屬于動(dòng)態(tài)語言的,但是因?yàn)榉瓷錂C(jī)制,可以在程序運(yùn)行期間加載、探知和使用編譯期間完全未知的類,并可以生成相關(guān)類對象的實(shí)例,從而調(diào)用某個(gè)方法和改變某個(gè)屬性值。因此,Java屬于半個(gè)動(dòng)態(tài)性語言。


1.反射機(jī)制

概念
Java中的反射機(jī)制是指在運(yùn)行狀態(tài)中,對于任何一個(gè)類都能知道這個(gè)類的所有屬性和方法;并且對于任意一個(gè)對象,都能調(diào)用它的方法;這種動(dòng)態(tài)獲取信息和調(diào)用對象方法的功能即為Java語言的反射機(jī)制。

使用場合(為什么要使用反射)
我們學(xué)習(xí)過多態(tài),知道編譯類型和運(yùn)行類型不一致的時(shí)候,當(dāng)我們想調(diào)用運(yùn)行類型特中的特有方法的時(shí)候需要強(qiáng)制轉(zhuǎn)換,因?yàn)槲覀兛赡苤肋@個(gè)對象屬于那些類。
但是當(dāng)我們在實(shí)際開發(fā)過程中,可能會(huì)有某個(gè)外部對象傳入過來,我們需要使用這個(gè)對象獨(dú)有的方法,那在編譯的過程中而我們根本無法知道這個(gè)對象屬于哪些類,只有運(yùn)行的時(shí)候我們才能知道傳入的對象和類的信息,那么這個(gè)時(shí)候就要用到反射了!

2.反射使用

①反射API

  • Class類(java.lang.Class):反射的核心類,可以獲取類的屬性、方法等信息。
  • Field類(java.lang.reflec):表示類中的成員變量,可以獲取和設(shè)置類中的屬性值。
  • Method類(Java.lang.reflec):表示類的方法,可以獲取方法中的信息或執(zhí)行方法。
  • Constructor類(Java.lang.reflec):表示類的構(gòu)造方法。

②獲取方法和屬性等信息
大致步驟:獲取想要的類的Class對象—>調(diào)用Class類中的方法—>
使用反射API來操作這些信息。

獲取Class對象的幾個(gè)方法

  • Class clazz = 對象名(想要的類的對象).getClass();
  • Class clazz = 類名.getClass();
  • 使用Class類中的forName()靜態(tài)方法(最安全、性能最好)
    ==Class clazz = Class.forName("類的安全路徑");==(通常使用這個(gè))

簡單案例:

package com.xsl.reflectTest;

/**
 * 測試類Person,通過反射獲取這個(gè)類的方法和屬性
 */
public class Person {
    private String name;
    private String gender;
    private int age;

    public Person() {}
    public Person(String name, String gender, int age) {
        this.name = name;
        this.gender = gender;
        this.age = age;
    }
    //getter和setter方法
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getGender() {
        return gender;
    }
    public void setGender(String gender) {
        this.gender = gender;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

    public String toString(){
        return "姓名:"+name+"  性別:"+gender+"  年齡:"+age;
    }
}

接下來我們通過反射來獲取Person類中的方法、屬性等信息

package com.xsl.reflectTest;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectTest {
    public static void main(String[] args) {
        try {
            //獲取Person類的Class對象
            Class clazz = Class.forName("com.xsl.reflectTest.Person");

            //獲取Person類的所有方法
            Method[] methods = clazz.getDeclaredMethods();
            for (Method m : methods) {
                System.out.println(m);
            }

            //獲取Person類的所有屬性
            Field[] fields = clazz.getDeclaredFields();
            for (Field  f : fields) {
                System.out.println(f);
            }

            //獲取Person類所有的構(gòu)造器
            Constructor[] constructors = clazz.getDeclaredConstructors();
            for(Constructor cons : constructors){
                System.out.println(cons);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
} 

輸出結(jié)果:
所有方法
public java.lang.String com.xsl.reflectTest.Person.getGender()
public void com.xsl.reflectTest.Person.setGender(java.lang.String)
public int com.xsl.reflectTest.Person.getAge()
public void com.xsl.reflectTest.Person.setAge(int)
public java.lang.String com.xsl.reflectTest.Person.toString()
public java.lang.String com.xsl.reflectTest.Person.getName()
public void com.xsl.reflectTest.Person.setName(java.lang.String)

所有屬性
private java.lang.String com.xsl.reflectTest.Person.name
private java.lang.String com.xsl.reflectTest.Person.gender
private int com.xsl.reflectTest.Person.age

所有構(gòu)造方法
public com.xsl.reflectTest.Person()
public com.xsl.reflectTest.Person(java.lang.String,java.lang.String,int)

③來創(chuàng)建對象為我們使用
創(chuàng)建對象的兩種方法:

  • 使用Class對象的==newInstance()==方法創(chuàng)建該Class對象對應(yīng)類的實(shí)例。(要求對應(yīng)的類要有空參構(gòu)造器)目前提示不推薦使用,即方法過時(shí)。【2018年】
  • 使用Class對象獲取指定的Constructor對象,再調(diào)用Constructor對象的newInstance()方法來創(chuàng)建對應(yīng)類的實(shí)例。(這個(gè)方法可以指定構(gòu)造方法創(chuàng)建實(shí)例)

簡單例子(延續(xù)上面的例子):

public class demo01 {
    public static void main(String[] args) throws Exception{
        //首先獲取Class對象
        Class clazz = Class.forName("com.xsl.reflectTest.Person");

        //采用第一種方法創(chuàng)建對象
        Person person = (Person) clazz.newInstance();//提示了不推薦使用,我們知道就好
        //設(shè)置屬性
        person.setName("劉備");
        person.setAge(20);
        person.setGender("男");
        System.out.println(person);//會(huì)默認(rèn)調(diào)用toString()方法,我們重寫了
        System.out.println("====================");
        Constructor constructor = clazz.getDeclaredConstructor(
                String.class,String.class,int.class);
        Person person1 = (Person) constructor.newInstance("孫尚香","女",16);
        System.out.println(person1);
    }
} 

運(yùn)行結(jié)果:
姓名:劉備 性別:男 年齡:20

姓名:孫尚香 性別:女 年齡:16


3.通過反射操作泛型、注解

①反射操作泛型
Java采用泛型擦除機(jī)制來引入泛型。Java中的泛型僅僅是給編譯器javac使用的,為確保數(shù)據(jù)的安全性和免去強(qiáng)制類型轉(zhuǎn)換的麻煩。
??為了通過反射操作這些類型以迎合開發(fā)的需要,Java新增了ParameterizedType、GenericArrayTypeTypeVariableWildcardType這幾個(gè)類型代表不能被歸一到Class類中的類型但是又和原始類型齊名的類型。

  • ParameterizedType:表示一種參數(shù)化的類型,比如Collection< String >
  • GenericArrayType:表示一種元素類型是參數(shù)化類型或者類型變量的數(shù)組類型。
  • TypeVariable:是各種類型變量的公共父接口。
  • WildcardType:代表一種通配符類型表達(dá)式,比如?、? extends Number、? super Integet。

簡單實(shí)例:

/*
    通過反射獲取泛型信息
 */
public class Demo {

    /*
        定義兩個(gè)帶泛型的方法
     */

    public static  void test01(Map<String,Person> map, List<Person> list){
        System.out.println("Demo01.test01()");
    }

    public Map<Integer,Person> test02(){
        System.out.println("Demo01.test02()");
        return null;
    }

    public static void main(String[] args) throws NoSuchMethodException {
        //獲取指定方法參數(shù)泛型信息
        Method m = Demo.class.getMethod("test01", Map.class, List.class);
        Type[] t = m.getGenericParameterTypes();

        for(Type paramType : t){
            System.out.println("#"+paramType);
            if(paramType instanceof ParameterizedType){
                //獲取泛型中的具體信息
                Type[] genericTypes = ((ParameterizedType) paramType).
                                        getActualTypeArguments();
                for (Type genericType : genericTypes){
                    System.out.println("泛型信息:"+genericType);
                }
            }
        }
        System.out.println();
        //獲得指定方法返回值泛型信息
        Method m2 = Demo.class.getMethod("test02",null);
        Type returnType = m2.getGenericReturnType();
        System.out.println("#"+returnType);
        if(returnType instanceof ParameterizedType){
            Type[] genericTypes = ((ParameterizedType) returnType).
                                        getActualTypeArguments();
            for(Type genericType : genericTypes){
                System.out.println("返回值,泛型類型:"+genericType);
            }
        }
    }
}


②反射操作注解
反射操作注解,即在上面注解中的注解器便是通過反射操作的!


4.反射性能

Method/Constructor/Field/Element都繼承了AccessibleObject,其中有一個(gè)方法setAccessible(boolean flag)方法,這個(gè)方法有兩個(gè)作用:

  1. 啟動(dòng)/禁止訪問安全檢查開關(guān):參數(shù)flag為true時(shí),反射的對象在使用時(shí),取消Java語言訪問檢查。默認(rèn)有false,實(shí)施Java語言訪問檢查。
  2. 禁止安全檢查,提高反射效率。

簡單實(shí)例:

public class TestReflect {
    public static void testNoReflect(){
        Person person = new Person();
        long startTimes = System.currentTimeMillis();
        for(int i = 0;i < Integer.MAX_VALUE; i++){
            person.getName();
        }
        long times = System.currentTimeMillis() - startTimes;
        System.out.println("沒有通過反射,調(diào)用方法消耗時(shí)間:"+times+"毫秒");
    }

    public static void testNoAccess() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Person person = new Person();
        Method method = Class.forName("com.xsl.reflectTest.Person").
                                    getMethod("getName");
        long startTimes = System.currentTimeMillis();
        for(int i =0;i < Integer.MAX_VALUE;i ++){
            method.invoke(person,null);
        }
        long times = System.currentTimeMillis() - startTimes;
        System.out.println("通過反射,沒有訪問權(quán)限,調(diào)用方法共消耗時(shí)間:"+
                        times+"毫秒");
    }

    public static void testUserAccess() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Person person = new Person();
        Method method = Class.forName("com.xsl.reflectTest.Person").
                getMethod("getName");
        method.setAccessible(true);
        long startTimes = System.currentTimeMillis();
        for(int i =0;i < Integer.MAX_VALUE;i ++){
            method.invoke(person,null);
        }
        long times = System.currentTimeMillis() - startTimes;
        System.out.println("通過反射,有訪問權(quán)限,調(diào)用方法共消耗時(shí)間:"+
                times+"毫秒");
    }

    public static void main(String[] args) throws Exception {
        testNoReflect();
        testNoAccess();
        testUserAccess();
    }
}

運(yùn)行結(jié)果:
沒有通過反射,調(diào)用方法消耗時(shí)間:4毫秒
通過反射,沒有訪問權(quán)限,調(diào)用方法共消耗時(shí)間:5077毫秒
通過反射,有訪問權(quán)限,調(diào)用方法共消耗時(shí)間:2611毫秒

雖然直接調(diào)用和反射調(diào)用消耗時(shí)間相差很大,但是這是我們將調(diào)用次數(shù)放大到了Integer.MAX_VALUE的結(jié)果,問題不大。但是禁止訪問檢查后,反射的效率確實(shí)提升了不少。


三、動(dòng)態(tài)代理

在了解動(dòng)態(tài)代理之前,我們先了解一下一種設(shè)計(jì)模式--代理模式--
同時(shí)對于代理,根據(jù)創(chuàng)建代理的時(shí)間點(diǎn),分為靜態(tài)代理和動(dòng)態(tài)代理。


1.代理模式

代理模式是Java常用的設(shè)計(jì)模式,特征是代理類和委托類有==同樣的接口==,代理類的工作主要是負(fù)責(zé)為委托類預(yù)處理消息、過濾消息、把消息轉(zhuǎn)發(fā)給委托類以及事后處理消息等。
代理類和委托類之間通常會(huì)存在關(guān)聯(lián)關(guān)系,一個(gè)代理類的對象與一個(gè)委托類的對象關(guān)聯(lián),代理類對象本身并不真正實(shí)現(xiàn)服務(wù),而是通過調(diào)用委托類的對象的相關(guān)方法,來提供特定服務(wù)。即我們訪問實(shí)際對象時(shí),是通過代理對象來訪問的。代理模式就是在訪問實(shí)際對象時(shí)引入一定程度的間接性,通過這種間接性,可以附加多種用途。


2.靜態(tài)代理

何為靜態(tài)代理?
由程序員創(chuàng)建或特定工具自動(dòng)生成源代碼,也就是在編譯時(shí)就已經(jīng)將接口、被代理類、代理類等確定下來。在運(yùn)行之前代理類的.class文件就已經(jīng)生成。
靜態(tài)代理的簡單實(shí)現(xiàn):
首先我們定義好==公共接口==:

/*
    學(xué)生(接口)-->就是 被代理類(學(xué)生) 和代理類(班長)的公共接口
 */
public interface Person {
    //教班費(fèi)的行為
    void giveMoney();
}

定義好==被代理類==:

/*
    Student類實(shí)現(xiàn)Person接口,并實(shí)現(xiàn)交班費(fèi)的功能
 */
public class Student implements Person{
    private String name;
    public Student(String name){
        this.name = name;
    }
    @Override
    public void giveMoney() {
        System.out.println(name+"交了班費(fèi)100塊");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

然后定義==代理類==:

/*
    學(xué)生代理類,也實(shí)現(xiàn)了Person接口,保存一個(gè)學(xué)生實(shí)體,如此就可以代理學(xué)生產(chǎn)生行為
 */
public class StudentProxy implements Person{
    //被代理的學(xué)生
    Student stu;

    public StudentProxy(Student stu){
        //只代理學(xué)生
        if(stu.getClass() == Student.class){
            this.stu = stu;
        }
    }

    //代理交班費(fèi),調(diào)用被代理學(xué)生上交班費(fèi)行為
    @Override
    public void giveMoney() {
        stu.giveMoney();
    }
}

最后我們來測試一下:

public class Test {
    public static void main(String[] args) {
        //被代理的學(xué)生曹操,他的班費(fèi)上交由代理對象monitor班長完成
        Student caocao = new Student("曹操");

        //生成代理對象,并將曹操傳給代理對象
        Person monitor = new StudentProxy(caocao);

        //班長代理上交班費(fèi)
        monitor.giveMoney();
    }
}

運(yùn)行結(jié)果:
曹操交了班費(fèi)100塊

從測試及結(jié)果上看,我們并沒有直接通過Student類對象caocao來執(zhí)行交班費(fèi)的行為,而是通過StudentProxy類對象monitor來代理執(zhí)行的,這種模式就是代理模式。
代理模式:公共接口(Person),被代理類(Student),代理類(StudentProxy),而且代理類要有被代理類的具體實(shí)例,才能通過這個(gè)間接的調(diào)用方法

那么就有疑問了?這樣做有什么意義呢?
這個(gè)意義就在于代理模式的間接性,通過間接性,我們可以在代理過程中加入其它的用途或者是處理。
簡單的說,在交班費(fèi)之前,先來評價(jià)一下Student對象caocao,修改一下代碼:

/*
    學(xué)生代理類,也實(shí)現(xiàn)了Person接口,保存一個(gè)學(xué)生實(shí)體,如此就可以代理學(xué)生產(chǎn)生行為
 */
public class StudentProxy implements Person{
    //被代理的學(xué)生
    Student stu;

    public StudentProxy(Student stu){
        //只代理學(xué)生
        if(stu.getClass() == Student.class){
            this.stu = stu;
        }
    }

    //代理交班費(fèi),調(diào)用被代理學(xué)生上交班費(fèi)行為
    @Override
    public void giveMoney() {
        //在這里添加就可以了
        System.out.println(stu.getName()+"學(xué)習(xí)很好,就是疑心太大,老說有人偷他作業(yè)");
        stu.giveMoney();
    }
}

重新執(zhí)行:
曹操學(xué)習(xí)很好,就是疑心太大,老說有人偷他作業(yè)
曹操交了班費(fèi)100塊


我們可以代理過程中切入一些其他的操作。
這個(gè)可以看Spring的面向切面編程(AOP),在一個(gè)切點(diǎn)前執(zhí)行操作,在一個(gè)切點(diǎn)后執(zhí)行操作。


3.動(dòng)態(tài)代理

動(dòng)態(tài)代理就是代理類在程序運(yùn)行時(shí)創(chuàng)建的方式。
??對于靜態(tài)代理,代理類是我們已經(jīng)定義好的,在程序運(yùn)行之前就編譯完成的。而動(dòng)態(tài)代理中,代理類不是在Java代碼中定義的,而是在運(yùn)行期間根據(jù)我們在Java代碼中的“提示”動(dòng)態(tài)生成的。

那么相比于靜態(tài)代理,動(dòng)態(tài)代理有什么好處呢?
動(dòng)態(tài)代理的優(yōu)勢可以很方便的對代理類的函數(shù)進(jìn)行統(tǒng)一處理,而不用修改每個(gè)代理類中的方法。

用上面靜態(tài)代理中代理類StudentProxy中的giveMoney()方法舉例子:

    @Override
    public void giveMoney() {
          //調(diào)用被代理方法前我們加入個(gè)處理方法
          beforeMethod(); 

          stu.giveMoney();//被代理方法
    }

那好,這里我們只有一個(gè)giveMoney()方法,我們也只需要寫一次beforeMethod()處理方法,但是如果多了很多其他方法呢,我們是不是也要在每個(gè)方法中,都寫一次處理方法呢?
所以我們通過動(dòng)態(tài)代理可以解決這個(gè)問題:

動(dòng)態(tài)代理的簡單實(shí)現(xiàn):
首先,我們需要java.lang.reflect包下的==Proxy類==和一個(gè)==InvocationHandler==接口,通過這2個(gè)工具我們可以生成JDK動(dòng)態(tài)代理類和動(dòng)態(tài)代理對象。

基本步驟

  • 創(chuàng)建一個(gè)==InvocationHandler對象==
//MyInvocationHandler<Person>(stu)這個(gè)是我們實(shí)現(xiàn) 
//InvocationHandler接口的類
InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
  • 使用Proxy類的==getProxyClass靜態(tài)方法==生成一個(gè)動(dòng)態(tài)代理類對象StuProxyClass
Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(),new Class<?>[]{Person.class});
  • 獲得stuProxyClass中一個(gè)帶InvocationHandler參數(shù)的constructor
Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.calss);
  • 通過構(gòu)造器constructor來創(chuàng)建動(dòng)態(tài)實(shí)例stuProxy
Person stuProxy = (Person)constructor.newInstance(stuHandler);

OK,動(dòng)態(tài)代理對象創(chuàng)建完畢。
上面4個(gè)步驟也可以分成2個(gè)步驟完成:

//創(chuàng)建一個(gè)與動(dòng)態(tài)代理對象相關(guān)聯(lián)的InvocationHandler
InvocationHandle stuHandler = new MyInvocationHandler<Person>(stu);
//創(chuàng)建一個(gè)代理對象stuProxy,代理對象的每個(gè)方法執(zhí)行
//都會(huì)替換執(zhí)行Invocation中的invoke方法
Person stuProxy = (Person)Proxy.newProxyInstance(Person.class.getClassLoader(),new Class<?>[]{Person.class},stuHandler);

我們基本上以上面的例子來展示:

首先我們創(chuàng)建一個(gè)==接口==:

/*
    創(chuàng)建接口
 */
public interface Person {
    //交班費(fèi)
    void giveMoney();
    //學(xué)習(xí)
    void study();
}

然后是需要==被代理的類==:

/*
    創(chuàng)建需要被代理的類
 */
public class Student implements Person {
    private String name;

    public Student(String name) {
        this.name = name;
    }


    @Override
    public void giveMoney() {
        try {
            //假設(shè)數(shù)錢用時(shí)1秒
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name+"交了班費(fèi)50元");
    }

    @Override
    public void study() {
        System.out.println(name+"正在學(xué)習(xí)");
    }
}

然后我們創(chuàng)建一個(gè)工具類,用來打印方法執(zhí)行的時(shí)間:

/**
 *  檢測方法執(zhí)行時(shí)間的工具類:
 *      即在方法執(zhí)行前調(diào)用start方法,方法結(jié)束后調(diào)用finsh方法,即可
 *      計(jì)算出方法執(zhí)行的時(shí)間
 */
public class MonitorUti {
    private static ThreadLocal<Long> t = new ThreadLocal<>();

    public static void start(){
        t.set(System.currentTimeMillis());
    }

    public static void finsh(String methodName){
        long endTimes = System.currentTimeMillis();
        System.out.println(methodName+"方法一共耗時(shí)"+(endTimes - t.get())+"毫秒");
    }

}

再實(shí)現(xiàn)InvocationHandler接口,并持有代理對象實(shí)例

public class stuInvocationHandler<T> implements InvocationHandler {
    //invocationHandler持有的被代理對象
    T target;

    public stuInvocationHandler(T target){
        this.target =target;
    }

    /**
     *
     * @param proxy 代表動(dòng)態(tài)代理對象
     * @param method    正在執(zhí)行的方法
     * @param args  調(diào)用目標(biāo)方法時(shí)傳入的實(shí)參
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理執(zhí)行"+method.getName()+"方法");

        //代理過程中插入檢測方法,計(jì)算方法耗時(shí)
        MonitorUti.start();
        Object result = method.invoke(target,args);
        MonitorUti.finsh(method.getName());

        return result;
    }
}

最后我們直接測試:

public class ProxyTest {
    public static void main(String[] args) throws InterruptedException {
        //創(chuàng)建一個(gè)實(shí)例對象,這個(gè)對象是被代理的對象
        Person caocao = new Student("曹操");

        //創(chuàng)建一個(gè)與代理對象相關(guān)聯(lián)的InvocationHandler
        InvocationHandler stuHandler = new stuInvocationHandler<Person>(caocao);

        /*
        創(chuàng)建一個(gè)代理對象來代理caocao,
        代理對象的每個(gè)執(zhí)行方法都會(huì)替換執(zhí)行Invocation中的invoke方法
        */
        Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(),
                new Class[]{Person.class},stuHandler);
        //執(zhí)行交班費(fèi)的方法
        stuProxy.giveMoney();
        stuProxy.study();

    }
}

運(yùn)行結(jié)果:
代理執(zhí)行g(shù)iveMoney方法
曹操交了班費(fèi)50元
giveMoney方法一共耗時(shí)1012毫秒
代理執(zhí)行study方法
曹操正在學(xué)習(xí)
study方法一共耗時(shí)0毫秒

結(jié)論:
我們發(fā)現(xiàn),我們調(diào)用被代理類方法的時(shí)候,我們并沒有修改其任何代碼,但是執(zhí)行結(jié)果卻改變了了,我們只是在StuInvocationHadnler中修改了幾行代碼而已,我們調(diào)用被代理類的任何方法,運(yùn)行時(shí)都被添加了我們自定義的工具類的方法。
這就是動(dòng)態(tài)代理帶來的改變,但是具體是為什么了?

4.動(dòng)態(tài)代理原理

我們先來慢慢理清楚:

  1. 我們有==被代理類==
  2. 我們有==公共接口(所有的抽象方法被代理類都要實(shí)現(xiàn)的)==
  3. 我們有實(shí)現(xiàn)了InvocationHandler接口的類,我們叫它==調(diào)用處理器==(這里構(gòu)造參數(shù)T,表示泛型,這樣無論被代理類是什么類型,都能傳入了)
  4. 最后我們通過Proxy類的靜態(tài)方法創(chuàng)建了代理類對象

大致圖我們看下:
[圖片上傳失敗...(image-fb521e-1540044443552)]

所以,終歸那么多,最主要的核心還是
Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class},stuHandler);
這段代碼,這段代碼究竟做了什么呢?
經(jīng)過我們分析源碼,一層一層解刨,在Java虛擬機(jī)緩存中,找到了生成的代理類:
public final class $Proxy0 extends Proxy implements Person
這個(gè)代理類的構(gòu)造器:

 public $Proxy0(InvocationHandler paramInvocationHandler){
    super(paramInvocationHandler);
  }

這個(gè)父類構(gòu)造器有一個(gè)參數(shù)InvocationHandler paramInvocationHandler
這不就是我們傳入的==調(diào)用處理器對象==嗎?
再來看父類Proxy源碼:

//這里正好有個(gè)this.h就是InvocationHandler類型?。。?protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }

因此,子類$Proxy0中不正好也有了這個(gè)對象嗎?
繼續(xù)分析代理類源碼:

//在靜態(tài)代碼塊中有這樣的一行代碼
m3 = Class.forName("proxy.Person").getMethod("giveMoney", new Class[0]);

這不正是我們代理類的嗎,通過反射獲取方法,然后繼續(xù)找:

//這里重寫了接口Person的方法
  public final void giveMoney()
    throws 
  {
    try
    {
      //上面我們知道h是我們傳入的參數(shù)“調(diào)用處理器”對象
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

終于撥開云霧了:
this.h.invoke(this, m3, null);這行代碼不就是直接調(diào)用了我們“調(diào)用處理器”中實(shí)現(xiàn)的invoke方法嗎?
到這,終于知道動(dòng)態(tài)反射的一系列過程了?。。?!


5.總結(jié)

從源碼可以看出,生成的代理類是繼承了Proxy類,所以,動(dòng)態(tài)代理只能對接口來代理。

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

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

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