代理模式是Java常見的設計模式之一。所謂代理模式是指客戶端並不直接調用實際的對象,而是經過調用代理,來間接的調用實際的對象。
爲何要採用這種間接的形式來調用對象呢?通常是由於客戶端不想直接訪問實際的對象,或者訪問實際的對象存在困難,所以經過一個代理對象來完成間接的訪問。java
從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)的接口,若是接口增長方法,則代理類也必須跟着修改。其次,代理類每個接口對象對應一個委託對象,若是委託對象很是多,則靜態代理類就很是臃腫,難以勝任。框架
動態代理有別於靜態代理,是根據代理的對象,動態建立代理類。這樣,就能夠避免靜態代理中代理類接口過多的問題。動態代理是實現方式,是經過反射來實現的,藉助Java自帶的java.lang.reflect.Proxy
,經過固定的規則生成。
其步驟以下:ide
InvocationHandler
接口,並重寫該invoke
方法第一二步驟,和靜態代理同樣,不過說了。第三步,代碼以下:函數
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
。該方法的三個參數分別是:
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; } }
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"); } }