複習寶典之設計模式

查看更多寶典,請點擊《金三銀四,你的專屬面試寶典》html

第四章:設計模式

設計模式是前人(通常都是大師)對程序設計經驗的總結,學習並應用設計模式可使咱們開發的項目更加規範、便於擴展和維護。程序員

1)面向對象四大特徵

封裝:即將對象封裝成一個高度自治和相對封閉的個體,對象狀態(屬性)由這個對象本身的行爲(方法)來讀取和改變。面試

張三這我的,他的姓名等屬性,要有本身提供的獲取或改變的方法來操做。算法

private name setName getName編程

抽象:就是找出一些事物的類似和共性之處,而後將這些事物歸爲一個類,這個類只考慮這些事物的類似和共性之處,而且會忽略與當前主題和目標無關的那些方面,將注意力集中在與當前目標有關的方面。 就是把現實生活中的對象,抽象爲類。設計模式

繼承:在定義和實現一個類的時候,能夠在一個已經存在的類的基礎之上來進行,把這個已經存在的類所定義的內容做爲本身的內容,並能夠加入若干新的內容,或修改原來的方法使之更適合特殊的須要,這就是繼承。遺產繼承緩存

多態:是指程序中定義的引用變量所指向的具體類型和經過該引用變量發出的方法調用在編程時並不肯定,而是在程序運行期間才肯定,即一個引用變量倒底會指向哪一個類的實例對象,該引用變量發出的方法調用究竟是哪一個類中實現的方法,必須在由程序運行期間才能決定。安全

Object obj = new xxx();網絡

UserDao userDao = new UserDaoJdbcImpl();多線程

UserDao userDao = new UserDaoHibernateImpl();

靠的是父類或接口定義的引用變量能夠指向子類或具體實現類的實例對象(里氏替換原則),而程序調用的方法在運行期才動態綁定,就是引用變量所指向的具體實例對象的方法,也就是內存里正在運行的那個對象的方法,而不是引用變量的類型中定義的方法。

擴展:一個程序就是一個世界,世界由不一樣的種類組成,每一個類別下的個體就是一個個對象。封裝是對對象屬性的私有化,而後經過公開的方法進行訪問。繼承是is-a的關係,子類會繼承父類的非私有屬性與方法。多態分靜態的多態(構造函數的重載)與動態的多態(編譯時多態,里氏替換原則),表面意義是一種類型在不一樣狀況下的不一樣形態【如類的多態,行爲(接口)的多態】。

 

2)五大設計原則

單一職責原則(即一個類只負責一項職責

  單一職責原則(Single Responsibility Principle,SRP):就一個類而言,應該僅有一個引發它變化的緣由。即一個類應該只負責一個功能領域中的相應職責。

  單一職責原則是實現高內聚、低耦合的指導方針,它是最簡單但又最難運用的原則,須要設計人員發現類的不一樣職責並將其分離,而發現類的多重職責須要設計人員具備較強的分析設計能力和相關實踐經驗。

開閉原則(一個類或方法應該對擴展開放,對修改關閉

  開閉原則(Open-Closed Principle,OCP): 是指軟件實體(類、模塊、函數等等)應該能夠擴展,可是不可修改。即軟件實體應該儘可能在不修改原有代碼的狀況下進行擴展。

  爲了知足開閉原則,須要對系統進行抽象化設計,抽象化是開閉原則的關鍵。

里氏替換原則(子類能夠擴展父類的功能,但不能改變父類原有的功能,子類對象實例化父類對象

  里氏替換原則(Liskov Substitution Principle,LSP):全部引用父類的地方必須可以透明的使用子類的對象。即子類型必須可以替換掉它們的父類型。

  里氏替換原則告訴咱們,在軟件中將一個基類對象替換成它的子類對象,程序將不會產生任何錯誤和異常,反過來則不成立,若是一個軟件實體使用的是一個子類對象的話,那麼它不必定可以使用基類對象。所以在程序中儘可能使用基類類型來對對象進行定義,而在運行時再肯定其子類類型,用子類對象來替換父類對象。同時,里氏代換原則是實現開閉原則的重要方式之一。

它包含如下含義:

1) 子類能夠實現父類的抽象方法,但不能覆蓋父類的非抽象方法。

2) 子類的方法重載父類的方法時,方法的入參要比父類方法的入參更寬鬆。

3) 子類的方法實現父類的抽象方法時,方法的返回值要比父類更嚴格。

依賴倒置原則(上層模塊不該該依賴低層模塊的實現,而應該依賴其抽象

  依賴倒置原則(Dependency Inversion Principle,DIP):抽象不該該依賴細節,細節應該依賴於抽象。即應該針對接口編程,而不是針對實現編程。

  在大多數狀況下,咱們會同時使用開閉原則、里氏代換原則和依賴倒轉原則,開閉原則是目標,里氏代換原則是基礎,依賴倒轉原則是手段。

依賴倒置的優勢:減小類之間的耦合,提升系統的穩定性,減小並行開發引發的風險。

接口隔離原則(一個類對另外一個類的依賴應該創建在最小的接口上

  接口隔離原則(Interface Segregation Principle,ISP):使用專門的接口,而不使用單一的總接口,即客戶端不該該依賴那些它不須要的接口。

  根據接口隔離原則,當一個接口太大時,咱們須要將它分割成一些更細小的接口,使用該接口的客戶端僅需知道與之相關的方法便可。每個接口應該承擔一種相對獨立的角色,不幹不應乾的事,該乾的事都要幹。

 

3)設計模式基礎

通常地設計模式分爲三類共23種:

3.1. 建立型模式

單例模式、抽象工廠模式、建造者模式、工廠模式、原型模式。

這些設計模式提供了一種在建立對象的同時隱藏建立邏輯的方式,而不是使用 new 運算符直接實例化對象。這使得程序在判斷針對某個給定實例須要建立哪些對象時更加靈活。

3.2. 結構型模式

適配器模式、橋接模式、裝飾模式、組合模式、外觀模式、享元模式、代理模式。

這些設計模式關注類和對象的組合。繼承的概念被用來組合接口和定義組合對象得到新功能的方式。

3.3. 行爲型模式

模版模式、命令模式、迭代器模式、觀察者模式、中介者模式、備忘錄模式、解釋器模式、狀態模式、策略模式、責任鏈模式、訪問者模式。

這些設計模式特別關注對象之間的通訊。

 

4)單例模式

確保一個類最多隻有一個實例,並提供一個全局訪問點。

有些對象咱們只須要一個:線程池,緩存,硬件設備,若是多個實例會形成衝突,結果不一致等問題,能夠用靜態變量來實現,或者程序員之間協商一個全局變量。

具體實現原理:能夠將一個類的構造函數私有化,而後在該類內部建立本身的對象。

飽漢模式:無論是否用到,都先建立好

/**
* @author: 肖德子裕
* @date: 2019/02/28 20:50
* @description: 飽漢模式
*/
public class Singleton2 {
  private static Singleton2 uniqeInstance=new Singleton2();

  private Singleton2() {

  }

  public static Singleton2 getInstance(){
      return uniqeInstance;
  }
}

 

飢漢模式:須要時才建立

/**
* @author: 肖德子裕
* @date: 2019/02/28 20:50
* @description: 飢漢模式
*/
public class Singleton {
  private static Singleton uniqeInstance=null;

  private Singleton() {

  }

  public static Singleton getInstance(){
      if (uniqeInstance==null){
          uniqeInstance=new Singleton();
      }
      return uniqeInstance;
  }
}

 

在多線程的狀況下,可能2個線程同時訪問該方法,這樣的話就會建立2個實例對象,因此使用雙重檢查加鎖解決該問題

public static ChocolateFactory getInstance() {
  if (uniqueInstance == null) {
      synchronized (ChocolateFactory.class) {
          if (uniqueInstance == null) {
              uniqueInstance = new ChocolateFactory();
          }
      }
  }
  return uniqueInstance;
}

擴展知識:

synchronized是Java中的關鍵字,是一種同步鎖。它修飾的對象有如下幾種:

  1. 修飾一個代碼塊,被修飾的代碼塊稱爲同步語句塊,其做用的範圍是大括號{}括起來的代碼,做用的對象是調用這個代碼塊的對象;

  2. 修飾一個方法,被修飾的方法稱爲同步方法,其做用的範圍是整個方法,做用的對象是調用這個方法的對象;

  3. 修改一個靜態的方法,其做用的範圍是整個靜態方法,做用的對象是這個類的全部對象;

  4. 修改一個類,其做用的範圍是synchronized後面括號括起來的部分,做用主的對象是這個類的全部對象。

修飾一個代碼塊:一個線程訪問一個對象中的synchronized(this)同步代碼塊時,其餘試圖訪問該對象的線程將被阻塞。

當兩個併發線程(thread1和thread2)訪問同一個對象(syncThread)中的synchronized代碼塊時,在同一時刻只能有一個線程獲得執行,另外一個線程受阻塞,必須等待當前線程執行完這個代碼塊之後才能執行該代碼塊。Thread1和thread2是互斥的,由於在執行synchronized代碼塊時會鎖定當前的對象,只有執行完該代碼塊才能釋放該對象鎖,下一個線程才能執行並鎖定該對象。

建立了兩個SyncThread的對象syncThread1和syncThread2,線程thread1執行的是syncThread1對象中的synchronized代碼(run),而線程thread2執行的是syncThread2對象中的synchronized代碼(run);咱們知道synchronized鎖定的是對象,這時會有兩把鎖分別鎖定syncThread1對象和syncThread2對象,而這兩把鎖是互不干擾的,不造成互斥,因此兩個線程能夠同時執行。

當一個線程訪問對象的一個synchronized(this)同步代碼塊時,另外一個線程仍然能夠訪問該對象中的非synchronized(this)同步代碼塊。

Synchronized修飾一個方法很簡單,就是在方法的前面加synchronized,public synchronized void method(){//todo}; synchronized修飾方法和修飾一個代碼塊相似,只是做用範圍不同,修飾代碼塊是大括號括起來的範圍,而修飾方法範圍是整個函數。

在用synchronized修飾方法時要注意如下幾點:

  1. synchronized關鍵字不能繼承。

  2. 在定義接口方法時不能使用synchronized關鍵字。

  3. 構造方法不能使用synchronized關鍵字,但可使用synchronized代碼塊來進行同步。

 

5)工廠模式

遵循依賴抽象原則:

變量不要持有具體類的引用;不要讓類繼承自具體類,要繼承自抽象類或接口;不要覆蓋基類中已經實現的方法。

5.1 簡單工廠模式:

定義了一個建立對象的類,由這個類來封裝實例化對象的行爲

問題:如一家披薩店,在下訂單時會根據不一樣的披薩實例化不一樣的對象,這樣之後每次提出新品種或者下架某品種,就要去修改訂單裏面的實例化操做。

以下能夠定義一個簡單的工廠類來封裝實例化過程:

/**
* 簡單工廠模式
*/
public class SimplePizzaFactory {
  /**
    * 傳入披薩的類型
    *
    * @param ordertype
    * @return
    */
  public Pizza CreatePizza(String ordertype) {
      Pizza pizza = null;

      if (ordertype.equals("cheese")) {
          pizza = new CheesePizza();
      } else if (ordertype.equals("greek")) {
          pizza = new GreekPizza();
      } else if (ordertype.equals("pepper")) {
          pizza = new PepperPizza();
      }
      return pizza;
  }
}

 

/**
* 披薩訂單模塊
*/
public class OrderPizza {
  SimplePizzaFactory mSimplePizzaFactory;

  public OrderPizza(SimplePizzaFactory mSimplePizzaFactory) {
    setFactory(mSimplePizzaFactory);
  }

  public void setFactory(SimplePizzaFactory mSimplePizzaFactory) {
    Pizza pizza = null;
    String ordertype;
    this.mSimplePizzaFactory = mSimplePizzaFactory;
    //工廠生產披薩並進行包裝發貨
    do {
        ordertype = gettype();
        pizza = mSimplePizzaFactory.CreatePizza(ordertype);
        if (pizza != null) {
          pizza.prepare();
          pizza.bake();
          pizza.cut();
          pizza.box();
        }
    } while (true);
  }

  private String gettype() {
    try {
        BufferedReader strin = new BufferedReader(new InputStreamReader(
              System.in));
        System.out.println("input pizza type:");
        String str = strin.readLine();
        return str;
    } catch (IOException e) {
        e.printStackTrace();
        return "";
    }
  }
}

 

/**
* 披薩店中購買披薩
*/
public class PizzaStroe {
  public static void main(String[] args) {
    SimplePizzaFactory mSimplePizzaFactory;
    OrderPizza mOrderPizza;
    mOrderPizza=new OrderPizza(new SimplePizzaFactory());
  }
}

 

5.2 方法工廠模式:

定義了一個建立對象的抽象方法,由子類決定要實例化的類。

問題:若是一家披薩店要在全國開連鎖,這樣地方的配料確定不同,因此單純的將工廠複製過去是不行的。

以下能夠將建立披薩的方法抽象化,讓子類繼承後去實例化具體的對象:

/**
* 抽象化下訂單的模塊
*/
public abstract class OrderPizza {
  public OrderPizza() {
      Pizza pizza = null;
      String ordertype;
      do {
          ordertype = gettype();
          pizza = createPizza(ordertype);

          pizza.prepare();
          pizza.bake();
          pizza.cut();
          pizza.box();
      } while (true);
  }

  abstract Pizza createPizza(String ordertype);

  private String gettype() {
      try {
          BufferedReader strin = new BufferedReader(new InputStreamReader(
                  System.in));
          System.out.println("input pizza type:");
          String str = strin.readLine();
          return str;
      } catch (IOException e) {
          e.printStackTrace();
          return "";
      }
  }
}

 

/**
* 倫敦的披薩店
*/
public class LDOrderPizza extends OrderPizza {
  @Override
  Pizza createPizza(String ordertype) {
      Pizza pizza = null;
      if (ordertype.equals("cheese")) {
          pizza = new LDCheesePizza();
      } else if (ordertype.equals("pepper")) {
          pizza = new LDPepperPizza();
      }
      return pizza;
  }
}

 

/**
* 紐約的披薩店
*/
public class NYOrderPizza extends OrderPizza {
  @Override
  Pizza createPizza(String ordertype) {
      Pizza pizza = null;
      if (ordertype.equals("cheese")) {
          pizza = new NYCheesePizza();
      } else if (ordertype.equals("pepper")) {
          pizza = new NYPepperPizza();
      }
      return pizza;
  }
}

 

/**
* 披薩店中購買披薩
*/
public class PizzaStroe {
  public static void main(String[] args) {
    OrderPizza mOrderPizza;
    mOrderPizza=new NYOrderPizza();
  }
}

 

5.3 抽象工廠模式

定義了一個接口用於建立相關或有依賴關係的對象族,而無需明確指定具體類。

/**
* 抽象工廠模式
*/
public interface AbsFactory {
  public Pizza CreatePizza(String ordertype) ;
}

 

/**
* 倫敦工廠
*/
public class LDFactory implements AbsFactory {
  @Override
  public Pizza CreatePizza(String ordertype) {
    Pizza pizza = null;
    if (ordertype.equals("cheese")) {
        pizza = new LDCheesePizza();
    } else if (ordertype.equals("pepper")) {
        pizza = new LDPepperPizza();
    }
    return pizza;
  }
}

 

/**
* 紐約工廠
*/
public class NYFactory implements AbsFactory {
  @Override
  public Pizza CreatePizza(String ordertype) {
    Pizza pizza = null;
    if (ordertype.equals("cheese")) {
        pizza = new NYCheesePizza();
    } else if (ordertype.equals("pepper")) {
        pizza = new NYPepperPizza();
    }
    return pizza;
  }
}

 

/**
* 下訂單的模塊
*/
public class OrderPizza {
  AbsFactory mFactory;

  public OrderPizza(AbsFactory mFactory) {
    setFactory(mFactory);
  }

  public void setFactory(AbsFactory mFactory) {
    Pizza pizza = null;
    String ordertype;
    this.mFactory = mFactory;
    do {
        ordertype = gettype();
        pizza = mFactory.CreatePizza(ordertype);
        if (pizza != null) {
          pizza.prepare();
          pizza.bake();
          pizza.cut();
          pizza.box();
        }
    } while (true);
  }

  private String gettype() {
    try {
        BufferedReader strin = new BufferedReader(new InputStreamReader(
              System.in));
        System.out.println("input pizza type:");
        String str = strin.readLine();
        return str;
    } catch (IOException e) {
        e.printStackTrace();
        return "";
    }
  }
}

 

/**
* 披薩店中購買披薩
*/
public class PizzaStroe {
  public static void main(String[] args) {
    OrderPizza mOrderPizza;
    mOrderPizza=new OrderPizza(new LDFactory());
  }
}

 

6)策略模式

分別封裝行爲接口,實現算法族,超類裏放行爲接口對象,在子類裏具體設置行爲對象。

策略模式注重功能實現。

原則:分離變化部分,封裝接口,基於接口編程各類功能,此模式讓行爲算法的變化獨立於算法的使用者。

將行爲定義爲接口:

/**
* 飛行
*/
public interface FlyBehavior {
  void fly();
}

具體的實現類:

/**
* 會飛
*/
public class GoodFlyBehavior implements FlyBehavior {
  @Override
  public void fly() {
      System.out.println("--GoodFly--");
  }
}

 

/**
* 不會飛
*/
public class NoFlyBehavior implements FlyBehavior {
  @Override
  public void fly() {
      System.out.println("--NoFly--");
  }
}

若是還有其餘行爲也能夠向上面同樣,先定義接口,在實現,好比叫聲能夠分呱呱叫還有其餘叫。

定義一個鴨子類:

/**
* 鴨子
*/
public abstract class Duck {
  /**
  * 飛行
  */
  FlyBehavior mFlyBehavior;
 
  /**
  * 叫聲
  */
  QuackBehavior mQuackBehavior;

  public abstract void display();

  public Duck() {

  }

  public void Fly() {
    mFlyBehavior.fly();
  }

  public void Quack() {
    mQuackBehavior.quack();
  }

  public void SetQuackBehavoir(QuackBehavior qb) {
    mQuackBehavior = qb;
  }

  public void SetFlyBehavoir(FlyBehavior fb) {
    mFlyBehavior = fb;
  }

  public void swim() {
    System.out.println("~~im swim~~");
  }
}

鴨子下的子類:

/**
* 綠頭鴨
*/
public class GreenHeadDuck extends Duck {
  public GreenHeadDuck() {
      //實例化具體的飛行方式
      mFlyBehavior = new GoodFlyBehavior();
      //實例化具體的叫聲
      mQuackBehavior = new GaGaQuackBehavior();
  }

  @Override
  public void display() {
      System.out.println("**GreenHead**");
  }
}

這樣就能實現按不一樣需求實現:

public class StimulateDuck {
  public static void main(String[] args) {
      GreenHeadDuck mGreenHeadDuck = new GreenHeadDuck();
      RedHeadDuck mRedHeadDuck = new RedHeadDuck();

      mGreenHeadDuck.display();
      mGreenHeadDuck.Fly();
      mGreenHeadDuck.Quack();
      mGreenHeadDuck.swim();

      mRedHeadDuck.display();
      mRedHeadDuck.Quack();
      mRedHeadDuck.swim();
      mRedHeadDuck.Fly();
  }
}

 

7)模板模式

封裝了一個算法步驟,並容許子類爲一個或多個步驟方法提供實現;模板模式可使子類在不改變算法結構的狀況下,從新定義算法中的某些步驟。

模板模式注重步驟實現。

/**
* @author: 肖德子裕
* @date: 2019/03/03 19:31
* @description: 模板模式
*/
public abstract class HotDrinkTemplate {
  /**
    * 這是一個模板方法
    */
  public final void prepareRecipe() {
      //燒水,固定
      boilWater();
      //喝什麼,由子類實現
      brew();
      //加水,固定
      pourInCup();
      //經過鉤子方法,讓子類肯定是否配料
      if (wantCondimentsHook()) {
          //加配料
          addCodiments();
      } else {
          System.out.println("不加配料");
      }
  }

  /**
    * 鉤子方法
    *
    * @return
    */
  public boolean wantCondimentsHook() {
      return true;
  }

  public final void boilWater() {
      System.out.println("Boiling water");
  }

  public abstract void brew();

  public final void pourInCup() {
      System.out.println("put in cup");
  }

  public abstract void addCodiments();
}

 

/**
* @author: 肖德子裕
* @date: 2019/03/03 19:52
* @description: 泡茶
*/
public class TeaWithHook extends HotDrinkTemplate{
  @Override
  public boolean wantCondimentsHook() {
      System.out.println("y/n:");
      BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
      String result="";
      try {
          result=in.readLine();
      }catch (IOException e){
          e.printStackTrace();
      }
      if (result.equals("n")){
          return false;
      }
      return true;
  }

  @Override
  public void brew() {
      System.out.println("泡茶");
  }

  @Override
  public void addCodiments() {
      System.out.println("加檸檬");
  }
}

 

/**
* @author: 肖德子裕
* @date: 2019/03/03 19:58
* @description: 測試
*/
public class Main {
  public static void main(String[] args) {
      TeaWithHook tea=new TeaWithHook();
      tea.prepareRecipe();
  }
}

 

模板方法模式的優勢

  • 封裝不變的內容,擴展可變部分,若是咱們要添加一個H3悍馬模型,只須要繼承父類就能夠了。

  • 提取公共部分代碼,便於維護

  • 行爲由父類控制,子類實現。基本方法是由子類實現的,所以子類能夠經過拓展的方法增長相應的功能,符合開閉原則。

模板方法模式的使用場景

  • 多個子類有公有的方法,而且邏輯基本相同時

  • 重複、複雜的算法,能夠把核心算法設計爲模板方法,周邊的細節則有各個子類實現

  • 代碼重構時,模板方法模式是一個常用的模式,把相同的代碼抽取到父類中,而後用鉤子方法約束其行爲。

 

鉤子方法源於設計模式中模板方法模式,模板方法模式中分爲兩大類:模版方法和基本方法,而基本方法又分爲:抽象方法,具體方法,鉤子方法。

好萊塢原則:明星無需聯繫經紀人問名具體流程,只需作好本身的事便可,經紀人會通知明星去哪裏作什麼。高層無需知道調用底層的細節,利於解耦。比如模板模式的子類無需知道模板的步驟,只需作好本身該實現的方法。

 

8)代理模式

爲一個對象提供一個替身,以控制這個對象的訪問;被代理的對象能夠是遠程對象,建立開銷大的對象或須要安全控制的對象;代理模式有不少變體,都是爲了控制與管理對象訪問。

擴展:RMI

RMI遠程方法調用是計算機之間經過網絡實現對象調用的一種通信機制;使用這種機制,一臺計算機上的對象能夠調用另一臺計算機上的對象來獲取遠程數據;在過去,TCP/IP通信是遠程通信的主要手段,面向過程開發;而RPC使程序員更容易地調用遠程程序,但在複雜的信息傳訊時,RPC依然未能很好的支持;RMI被設計成一種面向對象開發方式,容許程序員使用遠程對象來實現通訊。

代理模式分類:

靜態代理:在軟件設計時經常使用的代理通常是指靜態代理,也就是在代碼中顯式指定的代理。

/**
* @author: 肖德子裕
* @date: 2018/10/12 11:23
* @description: 用戶登陸接口
*/
public interface UserServer {
  void login();
}

 

/**
* @author: 肖德子裕
* @date: 2018/10/12 11:24
* @description: 用戶登陸實現類
*/
public class UserServerImpl implements UserServer{
  @Override
  public void login() {
      System.out.println("用戶登陸");
  }
}

 

/**
* @author: 肖德子裕
* @date: 2018/10/12 11:27
* @description: 靜態代理模式
*/
public class UserServerProxy implements UserServer{
  private UserServer userServer;

  public UserServerProxy(UserServer userServer){
      this.userServer=userServer;
  }

  @Override
  public void login() {
      before();
      userServer.login();
      after();
  }

  public void before(){
      System.out.println("登陸以前");
  }

  public void after(){
      System.out.println("登陸以後");
  }
}

 

/**
* @author: 肖德子裕
* @date: 2018/10/12 11:32
* @description: 測試靜態代理
*/
public class Main {
  public static void main(String[] args) {
      UserServer userServer=new UserServerProxy(new UserServerImpl());
      userServer.login();
  }
}

靜態代理的缺點很明顯:一個代理類只能對一個業務接口的實現類進行包裝,若是有多個業務接口的話就要定義不少實現類和代理類才行。並且,若是代理類對業務方法的預處理、調用後操做都是同樣的(好比:調用前輸出提示、調用後自動關閉鏈接),則多個代理類就會有不少重複代碼。這時咱們能夠定義這樣一個代理類,它能代理全部實現類的方法調用:根據傳進來的業務實現類和方法名進行具體調用,那就是動態代理。

 

JDK動態代理:JDK動態代理所用到的代理類在程序調用到代理類對象時才由JVM真正建立,JVM根據傳進來的 業務實現類對象 以及 方法名 ,動態地建立了一個代理類的class文件並被字節碼引擎執行,而後經過該代理類對象進行方法調用。**咱們須要作的,只需指定代理類的預處理、調用後操做便可。

1)被代理對象必需要實現接口,才能產生代理對象,若是沒有接口將不能使用動態代理技術;

2)動態代理可對方法進行加強,如增長事務的打開與提交;

3)在不破壞原有結構的狀況下,生成動態代理對象,對原有方法進行加強。

注意: JDK動態代理的代理對象在建立時,須要使用業務實現類所實現的接口做爲參數(由於在後面代理方法時須要根據接口內的方法名進行調用)。若是業務實現類是沒有實現接口而是直接定義業務方法的話,就沒法使用JDK動態代理了。而且,若是業務實現類中新增了接口中沒有的方法,這些方法是沒法被代理的(由於沒法被調用)。

public interface BookFacade {  
  public void addBook();  
}

 

public class BookFacadeImpl implements BookFacade {   
  @Override  
  public void addBook() {  
      System.out.println("增長圖書方法。。。");  
  }  
}

 

public class BookFacadeProxy implements InvocationHandler {  
//這其實業務實現類對象,用來調用具體的業務方法
  private Object target;
   
  /**
    * 綁定業務對象並返回一個代理類  
    */  
  public Object bind(Object target) {  
      this.target = target; //接收業務實現類對象參數

      //經過反射機制,建立一個代理類對象實例並返回。用戶進行方法調用時使用
      //建立代理對象時,須要傳遞該業務類的類加載器(用來獲取業務實現類的元數據,在包裝方法是調用真正的業務方法)、接口、handler實現類
      return Proxy.newProxyInstance(target.getClass().getClassLoader(),  
              target.getClass().getInterfaces(), this); }  
  /**
    * 包裝調用方法:進行預處理、調用後處理
    */  
  public Object invoke(Object proxy, Method method, Object[] args)  
          throws Throwable {  
      Object result=null;  

      System.out.println("預處理操做——————");  
      //調用真正的業務方法  
      result=method.invoke(target, args);  

      System.out.println("調用後處理——————");  
      return result;  
  }  
}  

 

public static void main(String[] args) {  
      BookFacadeImpl bookFacadeImpl=new BookFacadeImpl();
      BookFacadeProxy proxy = new BookFacadeProxy();  
      BookFacade bookfacade = (BookFacade) proxy.bind(bookFacadeImpl);  
      bookfacade.addBook();  
}  

 

在列舉一個動態代理的例子:

public interface UserDao {
  public String creatSqlSession();
}

 

public class UserDaoImpl implements UserDao{
  @Override
  public String creatSqlSession() {
      return "建立SQLSESSION對象";
  }
}

 

public class UserDaoProxyFactory implements InvocationHandler{
  private UserDao userDao;

  public UserDaoProxyFactory(UserDao userDao){
      this.userDao=userDao;
  }

  public UserDao getUserDaoProxy(){
      //生成動態代理
      UserDao userProxy=(UserDao) Proxy.newProxyInstance(UserDaoProxyFactory.class.getClassLoader(),
              UserDaoImpl.class.getInterfaces(),
              this);
      //返回一個動態代理對象
      return userProxy;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      Object invoke=method.invoke(userDao,args);
      return invoke;
  }
}

 

public class Main {
  public static void main(String[] args) {
      UserDao userDao=new UserDaoImpl();
      UserDaoProxyFactory factory=new UserDaoProxyFactory(userDao);
      UserDao userProxy=factory.getUserDaoProxy();
      System.out.println(userProxy.creatSqlSession());
      System.out.println(userProxy instanceof UserDaoImpl);
  }
}

CGLIB動態代理:cglib是針對類來實現代理的,原理是對指定的業務類生成一個子類,並覆蓋其中業務方法實現代理。由於採用的是繼承,因此不能對final修飾的類進行代理。

public class BookFacadeImpl1 {  
  public void addBook() {  
      System.out.println("新增圖書...");  
  }  
}  

 

public class BookFacadeCglib implements MethodInterceptor {  
  private Object target;//業務類對象,供代理方法中進行真正的業務方法調用
 
  //至關於JDK動態代理中的綁定
  public Object getInstance(Object target) {  
      this.target = target; //給業務對象賦值
      Enhancer enhancer = new Enhancer(); //建立增強器,用來建立動態代理類
      enhancer.setSuperclass(this.target.getClass()); //爲增強器指定要代理的業務類(即:爲下面生成的代理類指定父類)
      //設置回調:對於代理類上全部方法的調用,都會調用CallBack,而Callback則須要實現intercept()方法進行攔
      enhancer.setCallback(this);
      // 建立動態代理類對象並返回  
      return enhancer.create();
  }
  // 實現回調方法
  public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
      System.out.println("預處理——————");
      proxy.invokeSuper(obj, args); //調用業務類(父類中)的方法
      System.out.println("調用後操做——————");
      return null;
  }

 

public static void main(String[] args) {      
      BookFacadeImpl1 bookFacade=new BookFacadeImpl1();
      BookFacadeCglib cglib=new BookFacadeCglib();  
      BookFacadeImpl1 bookCglib=(BookFacadeImpl1)cglib.getInstance(bookFacade);  
      bookCglib.addBook();  
}  

總結:

靜態代理是經過在代碼中顯式定義一個業務實現類一個代理,在代理類中對同名的業務方法進行包裝,用戶經過代理類調用被包裝過的業務方法;

JDK動態代理是經過接口中的方法名,在動態生成的代理類中調用業務實現類的同名方法;

CGlib動態代理是經過繼承業務類,生成的動態代理類是業務類的子類,經過重寫業務方法進行代理;

相關文章
相關標籤/搜索