反射和代理(靜態代理和動態代理)

反射

Java的反射機制其實就是通過反射,可以在運行期間獲取、檢測和調用對象的屬性和方法。
用一句話來概括反射就是加載一個運行時才知道的類以及它的完整內部結構

反射的使用場景

1、編程工具 IDEA 或 Eclipse 等,在寫代碼時會有代碼(屬性或方法名)提示,就是因爲使用了反射。

2、很多知名的框架,爲了讓程序更優雅更簡潔,也會使用到反射。

例如,Spring 可以通過配置來加載不同的類,調用不同的方法,代碼如下所示:

<bean id="person" class="com.spring.beans.Person" init-method="initPerson"></bean>

例如,MyBatis 在 Mapper 使用外部類的 Sql 構建查詢時,代碼如下所示:

@SelectProvider(type = PersonSql.class, method = "getListSql")
List<Person> getList();
class PersonSql {
    public String getListSql() {
        String sql = new SQL() {{
            SELECT("*");
            FROM("person");
        }}.toString();
        return sql;
    }
}

3、數據庫連接池,也會使用反射調用不同類型的數據庫驅動,代碼如下所示:

String url = "jdbc:mysql://127.0.0.1:3306/mydb";
String username = "root";
String password = "root";
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(url, username, password);

反射的使用

反射相關類:

類名 說明
Class 類的實體,在運行的Java應用程序中表示類和接口
Field 類的成員變量(類的屬性)
Method 類的方法
Constructor 類的構造方法

獲得類(3種)

  • getClass()
String str = "aa";
Class c1 = str.getClass();
  • Class.forName()
Class c1 = Class.forName ("java.lang.String");
  • .class
Class c1 = String.class

補充:

TYPE(基本數據類型包裝類的 TYPE語法),例如:

System.out.println(int.class);//int
System.out.println(Integer.TYPE);//int
System.out.println(Integer.class);//class java.lang.Integer

int.class表示基本數據類型int的Class對象
TYPE是Integer中的靜態常量,api中已經寫明它表示的是基本數據類型int的class實例
Integer.class表示引用數據類型Integer的class對象

獲得屬性

方法 說明
public Field getField(String name) 此 Class 對象所表示的類或接口的指定公共成員字段
public Field[] getFields() 此 Class 對象所表示的類或接口的所有可訪問公共字段
public Field getDeclaredField(String name) 此 Class 對象所表示的類或接口的指定已聲明字段(包括私有屬性)
public Field[] getDeclaredFields() 此 Class 對象所表示的類或接口所聲明的所有字段(包括私有)

獲得方法

方法 說明
public Method getMethod(String name) 此 Class 對象所表示的類或接口的指定公共成員方法
public Method[] getMethods() 此 Class 對象所表示的類或接口(包括那些由該類或接口聲明的以及從超類和超接口繼承的那些的類或接口)的所有可訪問公共方法
public Method getDeclaredMethod(String name) 此 Class 對象所表示的類或接口的指定已聲明方法(包括私有)
public Method[] getDeclaredMethods() 此 Class 對象所表示的類或接口所聲明的所有方法(包括公共、保護、默認(包)訪問和私有方法,但不包括繼承的方法)

例子:

Class myClass = Class.forName("com.interview.chapter4.MyReflect");
//調用靜態方法,無需創建實例對象
Method method = myClass.getMethod("staticMd");
method.invoke(myClass);
// 創建實例對象(相當於 new )
Object object = myClass.newInstance();
//調用公共方法
Method method2 = myClass.getMethod("publicMd");
method2.invoke(object);
//調用私有方法
Method method3 = myClass.getDeclaredMethod("privateMd");
method3.setAccessible(true);
method3.invoke(object);

反射獲取調用類可以通過 Class.forName(),反射獲取類實例要通過 newInstance(),相當於 new 一個新對象,反射獲取方法要通過 getMethod(),獲取到類方法之後使用 invoke() 對類方法進行調用。如果是類方法爲私有方法的話,則需要通過 setAccessible(true) 來修改方法的訪問限制。

獲得類的構造器

方法 說明
public Constructor getConstructor(Class<?>… ) 此 Class 對象所表示的類的指定公共構造方法
public Constructor<?>[] getConstructors() 此 Class 對象所表示的類的所有公共構造方法
public Constructor< T> getDeclaredConstructor(Class<?>…) 此 Class 對象所表示的類或接口的指定構造方法
public Constructor<?>[] getDeclaredConstructors() 此 Class 對象表示的類聲明的所有構造方法。它們是公共、保護、默認(包)訪問和私有構造方法

新建類的實例

  • 調用類的Class對象的newInstance方法
  • 調用默認Constructor對象的newInstance方法
  • 調用帶參數Constructor對象的newInstance方法

代理

代理,就是你委託別人幫你辦事,所以代理模式也有人稱作委託模式的。比如領導要做什麼事,可以委託他的祕書去幫忙做,這時就可以把祕書看做領導的代理。

代理模式又分爲靜態代理和動態代理。

靜態代理

靜態代理的使用

靜態代理,代理類和被代理的類實現了同樣的接口,代理類同時持有被代理類的引用。當我們需要調用被代理類的方法時,可以通過調用代理類的方法來做到。舉例如下:

假設領導的工作是開會和給員工考評。

先定義一個接口Iwork

public interface IWork {
    void meeting();
    int evaluate(String name);
}

定義領導類Leader被代理的類,實現接口Iwork):

import java.util.Random;

public class Leader implements IWork {

    @Override
    public void meeting() {
        System.out.println("領導早上要組織會議");
    }
    @Override
    public int evaluate(String name) {
        int score = new Random(System.currentTimeMillis()).nextInt(20) + 80;
        System.out.println(String.format("領導給%s的考評爲%s分", name, score));
        return score;
    }
}

定義祕書類Secretary代理類,實現接口Iwork

public class Secretary implements IWork {
    private Leader mLeader;

    public Secretary(Leader mLeader) {
        this.mLeader = mLeader;
    }

    @Override
    public void meeting() {
        System.out.println("祕書先給老闆準備材料");
        mLeader.metting();
    }
    @Override
    public int evaluate(String name) {
        return mLeader.evaluate(name);
    }
}

測試類:

public class TestApp {
    public static void main(String[] args) {
        Leader leader = new Leader();
        Secretary secretary = new Secretary(leader);
        secretary.meeting();
        secretary.evaluate("Joy");
    }
}

運行結果:
在這裏插入圖片描述

調用Secretary類的 meeting 方法時,我們調用了Leader類的 meeting 的方法,在此之前,我們還擴充了該方法。這時有的人可能有疑惑了,這看起來有點是裝飾者模式了。這到底怎麼回事?

和裝飾者模式的區別

實際上,在裝飾器模式和代理模式之間還是有很多差別的。裝飾器模式關注於在一個對象上動態的添加方法,然而代理模式關注於控制對對象的訪問。換句話說,用代理模式,代理類(proxy class)可以對它的客戶隱藏一個對象的具體信息。因此,當使用代理模式的時候,我們常常在一個代理類中創建一個對象的實例。並且,當我們使用裝飾器模式的時候,我們通常的做法是將原始對象作爲一個參數傳給裝飾者的構造器

一句話來總結這些差別:使用代理模式,代理和真實對象之間的的關係通常在編譯時就已經確定了,而裝飾者能夠在運行時遞歸地被構造。

僞代碼如下:

代理模式:

Interface Subject {
    void doAction()
}

public class RealSubject implements Subject{
    @Override
    public void doAction() {};
}

public class Proxy implements Subject{
       private RealSubject realSubject;

       public Proxy(RealSubject realSubject) {
             //關係在編譯時確定
            this.realSubject = realSubject;
       }

       @Override
       public void doAction() {.
             realSubject.doAction();.
       }
}

裝飾者模式:

Interface Component {
    void doAction()
}

public class ConcreteComponent implements Component {
    @Override
    public void doAction() {};
}

public class Decorator implements Component {
       private Component component;

       public Decorator(Component component) {
             //關係在編譯時確定
            this.component = new component;
       }
       public void doAction() {.
             component.doAction();.
       }
}

其實代理模式和裝飾者模式側重點不一樣,代理模式重點在於明確了被代理的類。如上例中,祕書很明確要代理的是的領導。而裝飾者模式側重於拓展類的方法,裝飾類持有的實現Component接口的類的對象不是固定的,也就是說,裝飾類可以根據在調用時傳入的參數,裝飾任意一個實現了 Component 接口的類

優缺點

  • 優點:
    (1)代理使客戶端不需要知道實現類是什麼,怎麼做的,而客戶端只需知道代理即可(解耦合),對客戶端隱藏了具體信息。
  • 缺點:
    (1)代理類和委託類實現了相同的接口,代理類通過委託類實現了相同的方法。這樣就出現了大量的代碼重複。如果接口增加一個方法,除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了代碼維護的複雜度。
    (2)代理對象只服務於一種類型的對象,如果要服務多類型的對象。勢必要爲每一種對象都進行代理,靜態代理在程序規模稍大時就無法勝任了。即靜態代理類只能爲特定的接口(Service)服務。如想要爲多個接口服務則需要建立很多個代理類。

動態代理

正如前面說的,使用靜態代理的話,每個代理類只能爲一個接口服務,這樣程序開發中必然會產生許多的代理類。所以我們就會想辦法可以通過一個代理類完成全部的代理功能,那麼我們就需要用動態代理。

對靜態代理而言,一個代理只能代理一種類型,而且是在編譯期就已經確定被代理的對象。而動態代理是在運行時,通過反射機制實現動態代理,並且能夠代理各種類型的對象。

動態代理使用場景

動態代理被廣爲人知的使用場景是 Spring 中的面向切面編程(AOP)。例如,依賴注入 @Autowired 和事務註解 @Transactional 等,都是利用動態代理實現的。動態代理還可以封裝一些 RPC 調用,也可以通過代理實現一個全局攔截器等。

和反射的關係

JDK 原生提供的動態代理就是通過反射實現的,但動態代理的實現方式還可以是 ASM(一個短小精悍的字節碼操作框架)、cglib(基於 ASM)等,並不侷限於反射。

實現一:JDK原生動態代理

實現代碼如下:

interface Animal {
    void eat();
}
class Dog implements Animal {
    @Override
    public void eat() {
        System.out.println("The dog is eating");
    }
}
class Cat implements Animal {
    @Override
    public void eat() {
        System.out.println("The cat is eating");
    }
}

// JDK 代理類
class AnimalProxy implements InvocationHandler {
    private Object target; // 代理對象
    public Object getInstance(Object target) {
        this.target = target;
        // 取得代理對象
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("調用前");
        Object result = method.invoke(target, args); // 方法調用
        System.out.println("調用後");
        return result;
    }
}

public static void main(String[] args) {
    // JDK 動態代理調用
    AnimalProxy proxy = new AnimalProxy();
    Animal dogProxy = (Animal) proxy.getInstance(new Dog());
    dogProxy.eat();
}

JDK Proxy 只能代理實現接口的類(即使是 extends 繼承類也是不可以代理的)
爲什麼?
——這是由於 JDK 原生設計的原因,動態代理的實現方法 newProxyInstance() 的有一個參數爲interfaces,代表接口代理類的接口實現列表。因此,要使用 JDK 原生的動態只能通過實現接口來完成。

實現二:cglib動態代理

要使用cglib動態代理,需引入cglib依賴:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.12</version>
</dependency>

實現代碼如下:

class Panda {
    public void eat() {
        System.out.println("The panda is eating");
    }
}
class CglibProxy implements MethodInterceptor {
    private Object target; // 代理對象
    public Object getInstance(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        // 設置父類爲實例類
        enhancer.setSuperclass(this.target.getClass());
        // 回調方法
        enhancer.setCallback(this);
        // 創建代理對象
        return enhancer.create();
    }
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("調用前");
        Object result = methodProxy.invokeSuper(o, objects); // 執行方法調用
        System.out.println("調用後");
        return result;
    }
}
public static void main(String[] args) {
    // cglib 動態代理調用
    CglibProxy proxy = new CglibProxy();
    Panda panda = (Panda)proxy.getInstance(new Panda());
    panda.eat();
}

執行結果:

調用前
The panda is eating
調用後

由以上代碼可以知道,cglib 的調用通過實現 MethodInterceptor 接口的 intercept 方法,調用 invokeSuper 進行動態代理的。它可以直接對普通類進行動態代理,並不需要像 JDK 代理那樣,需要通過接口來完成

cglib 底層是通過子類繼承被代理對象的方式實現動態代理的,因此代理類不能是最終類(final),否則就會報錯 java.lang.IllegalArgumentException: Cannot subclass final class xxx。

兩種實現的區別

  1. JDK 原生動態代理是基於接口實現的,不需要添加任何依賴,可以平滑的支持 JDK 版本的升級
  2. cglib 不需要實現接口,可以直接代理普通類,需要添加依賴包,性能更高。

Spring中動態代理

Spring 動態代理的實現方式有兩種:cglib 和 JDK 原生動態代理。Spring默認使用JDK動態代理,在需要代理類而不是代理接口的時候,Spring會自動切換爲使用CGLIB代理,不過現在的項目都是面向接口編程,所以JDK動態代理相對來說用的還是多一些。

Java反射機制剖析
Java-反射和動態代理
靜態代理和動態代理
靜態代理和動態代理的區別

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章