【Java設計模式】之代理模式

代理模式是Java常見的設計模式之一。所謂代理模式是指客戶端並不直接調用實際的對象,而是經過調用代理,來間接的調用實際的對象。
爲何要採用這種間接的形式來調用對象呢?通常是由於客戶端不想直接訪問實際的對象,或者訪問實際的對象存在困難,所以經過一個代理對象來完成間接的訪問。java

代理模式的UML圖

從UML圖中,能夠看出代理類與真正實現的類都是繼承了抽象的主題類,這樣的好處在於代理類能夠與實際的類有相同的方法,能夠保證客戶端使用的透明性。web

代理模式的實現

代理模式能夠有兩種實現的方式,一種是靜態代理類,另外一種是各大框架都喜歡的動態代理。下面咱們主要講解一下這兩種代理模式設計模式

一、靜態代理

咱們先看針對上面UML實現的例子,再看靜態代理的特色。
Subject接口的實現數組

public interface Subject {
    void visit();
}

實現了Subject接口的兩個類:服務器

public class RealSubject implements Subject {

    private String name = "byhieg";
    @Override
    public void visit() {
        System.out.println(name);
    }
}
public class ProxySubject implements Subject{

    private Subject subject;

    public ProxySubject(Subject subject) {
        this.subject = subject;
    }

    @Override
    public void visit() {
        subject.visit();
    }
}

具體的調用以下:app

public class Client {

    public static void main(String[] args) {
        ProxySubject subject = new ProxySubject(new RealSubject());
        subject.visit();
    }
}

經過上面的代理代碼,咱們能夠看出代理模式的特色,代理類接受一個Subject接口的對象,任何實現該接口的對象,均可以經過代理類進行代理,增長了通用性。可是也有缺點,每個代理類都必須實現一遍委託類(也就是realsubject)的接口,若是接口增長方法,則代理類也必須跟着修改。其次,代理類每個接口對象對應一個委託對象,若是委託對象很是多,則靜態代理類就很是臃腫,難以勝任。框架

二、動態代理(JDK)

動態代理有別於靜態代理,是根據代理的對象,動態建立代理類。這樣,就能夠避免靜態代理中代理類接口過多的問題。動態代理是實現方式,是經過反射來實現的,藉助Java自帶的java.lang.reflect.Proxy,經過固定的規則生成。
其步驟以下:ide

  1. 編寫一個委託類的接口,即靜態代理的(Subject接口)
  2. 實現一個真正的委託類,即靜態代理的(RealSubject類)
  3. 建立一個動態代理類,實現InvocationHandler接口,並重寫該invoke方法
  4. 在測試類中,生成動態代理的對象。

第一二步驟,和靜態代理同樣,不過說了。第三步,代碼以下:函數

public class DynamicProxy implements InvocationHandler {
    private Object object;
    public DynamicProxy(Object object) {
        this.object = object;
    }

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

第四步,建立動態代理的對象測試

Subject realSubject = new RealSubject();
DynamicProxy proxy = new DynamicProxy(realSubject);
ClassLoader classLoader = realSubject.getClass().getClassLoader();
Subject subject = (Subject) Proxy.newProxyInstance(classLoader, new  Class[]{Subject.class}, proxy);
subject.visit();

建立動態代理的對象,須要藉助Proxy.newProxyInstance。該方法的三個參數分別是:

  • ClassLoader loader表示當前使用到的appClassloader。
  • Class<?>[] interfaces表示目標對象實現的一組接口。
  • InvocationHandler h表示當前的InvocationHandler實現實例對象。

 JDK代理模式

public class ProxyFactory {  
      
    private Object obj;  
      
      
    public ProxyFactory(Object obj) {  
        super();  
        this.obj = obj;  
    }  
  
    public Object getTransactionProxyInstance(){  
          
        Object proxy = Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {  
            /** 
             * 三個參數:一、代理對象,二、目標對象的方法,三、目標對象的參數值列表 
             */  
            @Override  
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
                System.out.println("開啓事務..."); //執行核心業務以前執行的內容  
                method.invoke(obj, args);      //執行目標對象方法,即核心業務  
                System.out.println("關閉事務..."); //執行核心業務以後執行的內容  
                return proxy;  
            }  
        });  
          
        return proxy;  
    }  
    
}

三、動態代理(CGLIB)

JDK動態代理機制只能代理實現接口的類,通常沒有實現接口的類不能進行代理。cglib就是針對類來實現代理的,它的原理是對指定目標類生成一個子類,並覆蓋其中方法實現加強,但由於採用的是繼承,因此不能對final修飾的類進行代理。

使用cglib實現動態代理,徹底不受代理類必須實現接口的限制,並且cglib底層採用ASM字節碼生成框架,使用字節碼技術生成代理類,比使用java反射效率要高。

須要引入兩個jar包:cglib.jar,asm.jar

定義了一個攔截器,在調用目標方法以前,cglib回調MethodInterceptor接口方法攔截,來實現本身的業務邏輯,相似

於JDK中的InvocationHandler接口。

@Override  
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable ;

proxy:爲cglib動態生成的代理實例

method:爲上文中實體類所調用的被代理的方法調用

args:爲method參數數值列表

methodProxy:爲生成代理類對方法的代理引用

返回:從代理實例方法調用返回的值

 

其中,methodProxy.invokeSuper(obj,arg):

調用代理類實例上的proxy方法的父類方法

 

UserDaoImpl.java

public class CglibProxyFactory {  
      
    private Object obj;  
  
    public CglibProxyFactory(Object obj) {  
        super();  
        this.obj = obj;  
    }  
      
    public Object getProxyFactory(){  
        //Enhancer類是cglib中的一個字節碼加強器,它能夠方便的爲你所要處理的類進行擴展  
        Enhancer enhancer = new Enhancer();  
        enhancer.setSuperclass(obj.getClass());//將目標對象所在的類做爲Enhaner類的父類  
        enhancer.setCallback(new MethodInterceptor() {  
            //經過實現MethodInterceptor實現方法回調  
            @Override  
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {  
                System.out.println("事務開啓...");  
                method.invoke(obj, args);  
                System.out.println("事務結束...");  
                  
                return proxy;  
            }  
        });  
          
        return enhancer.create();//生成目標對象並返回  
    }  
}

測試類

public class TestCglibProxy {  
      
    @Test  
    public void test1(){  
        UserDaoImpl userDao = new UserDaoImpl();  
        UserDaoImpl userDaoProxy = (UserDaoImpl) new CglibProxyFactory(userDao).getProxyFactory();  
        userDaoProxy.save();  
        System.out.println("目標對象類型:"+userDao.getClass());  
        System.out.println("代理對象類型:"+userDaoProxy.getClass());  
    }  
      
      
}

附動態加載類的原理:

一、類定義

public class Programmer {  
  
    public void code()  
    {  
        System.out.println("I'm a Programmer,Just Coding.....");  
    }  
}

二、自定義類加載器

public class MyClassLoader extends ClassLoader {  
  
    public Class<?> defineMyClass( byte[] b, int off, int len)   
    {  
        return super.defineClass(b, off, len);  
    }  
      
}

三、而後編譯成Programmer.class文件,在程序中讀取字節碼,而後轉換成相應的class對象,再實例化

package samples;  
  
import java.io.File;  
import java.io.FileInputStream;  
import java.io.FileNotFoundException;  
import java.io.IOException;  
import java.io.InputStream;  
import java.net.URL;  
  
public class MyTest {  
  
    public static void main(String[] args) throws IOException {  
        //讀取本地的class文件內的字節碼,轉換成字節碼數組  
        File file = new File(".");  
        InputStream  input = new FileInputStream(file.getCanonicalPath()+"\\bin\\samples\\Programmer.class");  
        byte[] result = new byte[1024];  
          
        int count = input.read(result);  
        // 使用自定義的類加載器將 byte字節碼數組轉換爲對應的class對象  
        MyClassLoader loader = new MyClassLoader();  
        Class clazz = loader.defineMyClass( result, 0, count);  
        //測試加載是否成功,打印class 對象的名稱  
        System.out.println(clazz.getCanonicalName());  
                  
               //實例化一個Programmer對象  
               Object o= clazz.newInstance();  
               try {  
                   //調用Programmer的code方法  
                    clazz.getMethod("code", null).invoke(o, null);  
                   } catch (IllegalArgumentException | InvocationTargetException  
                        | NoSuchMethodException | SecurityException e) {  
                     e.printStackTrace();  
                  }  
    }  
}

在運行期的代碼中生成二進制字節碼

因爲JVM經過字節碼的二進制信息加載類的,那麼,若是咱們在運行期系統中,遵循Java編譯系統組織.class文件的格式和結構,生成相應的二進制數據,而後再把這個二進制數據加載轉換成對應的類,這樣,就完成了在代碼中,動態建立一個類的能力了。

在運行時期能夠按照Java虛擬機規範對class文件的組織規則生成對應的二進制字節碼。當前有不少開源框架能夠完成這些功能,如ASM,Javassist。

Java字節碼生成開源框架介紹--ASM:

ASM 是一個 Java 字節碼操控框架。它可以以二進制形式修改已有類或者動態生成類。ASM 能夠直接產生二進制 class 文件,也能夠在類被加載入 Java 虛擬機以前動態改變類行爲。ASM 從類文件中讀入信息後,可以改變類行爲,分析類信息,甚至可以根據用戶要求生成新類。

不過ASM在建立class字節碼的過程當中,操縱的級別是底層JVM的彙編指令級別,這要求ASM使用者要對class組織結構和JVM彙編指令有必定的瞭解。

 

package samples;  
  
import java.io.File;  
import java.io.FileOutputStream;  
import java.io.IOException;  
  
import org.objectweb.asm.ClassWriter;  
import org.objectweb.asm.MethodVisitor;  
import org.objectweb.asm.Opcodes;  
public class MyGenerator {  
  
    public static void main(String[] args) throws IOException {  
  
        System.out.println();  
        ClassWriter classWriter = new ClassWriter(0);  
        // 經過visit方法肯定類的頭部信息  
        classWriter.visit(Opcodes.V1_7,// java版本  
                Opcodes.ACC_PUBLIC,// 類修飾符  
                "Programmer", // 類的全限定名  
                null, "java/lang/Object", null);  
          
        //建立構造函數  
        MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);  
        mv.visitCode();  
        mv.visitVarInsn(Opcodes.ALOAD, 0);  
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>","()V");  
        mv.visitInsn(Opcodes.RETURN);  
        mv.visitMaxs(1, 1);  
        mv.visitEnd();  
          
        // 定義code方法  
        MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "code", "()V",  
                null, null);  
        methodVisitor.visitCode();  
        methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",  
                "Ljava/io/PrintStream;");  
        methodVisitor.visitLdcInsn("I'm a Programmer,Just Coding.....");  
        methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println",  
                "(Ljava/lang/String;)V");  
        methodVisitor.visitInsn(Opcodes.RETURN);  
        methodVisitor.visitMaxs(2, 2);  
        methodVisitor.visitEnd();  
        classWriter.visitEnd();   
        // 使classWriter類已經完成  
        // 將classWriter轉換成字節數組寫到文件裏面去  
        byte[] data = classWriter.toByteArray();  
        File file = new File("D://Programmer.class");  
        FileOutputStream fout = new FileOutputStream(file);  
        fout.write(data);  
        fout.close();  
    }  
}

Java字節碼生成開源框架介紹--Javassist:

  Javassist是一個開源的分析、編輯和建立Java字節碼的類庫。是由東京工業大學的數學和計算機科學系的 Shigeru Chiba (千葉 滋)所建立的。它已加入了開放源代碼JBoss 應用服務器項目,經過使用Javassist對字節碼操做爲JBoss實現動態AOP框架。javassist是jboss的一個子項目,其主要的優勢,在於簡單,並且快速。直接使用java編碼的形式,而不須要了解虛擬機指令,就能動態改變類的結構,或者動態生成類。

下面經過Javassist建立上述的Programmer類:

 

import javassist.ClassPool;  
import javassist.CtClass;  
import javassist.CtMethod;  
import javassist.CtNewMethod;  
  
public class MyGenerator {  
  
    public static void main(String[] args) throws Exception {  
        ClassPool pool = ClassPool.getDefault();  
        //建立Programmer類       
        CtClass cc= pool.makeClass("com.samples.Programmer");  
        //定義code方法  
        CtMethod method = CtNewMethod.make("public void code(){}", cc);  
        //插入方法代碼  
        method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");  
        cc.addMethod(method);  
        //保存生成的字節碼  
        cc.writeFile("d://temp");  
    }  
}
相關文章
相關標籤/搜索