在java的項目中經常用到代理模式,它不是什麼高深的技術,只是一種解決問題的思路。
代理模式的由來
程序來源於生活,前賢的總結總是那麼精闢。在生活中處處可以見到代理模式的影子。如明星和經紀人與業務方(廣告商,導演),租房客和中介與房東,大姑娘和媒婆與小夥子。發現它們的規律了嗎?參與方總是有三方,通過中間商賺差價...呸,通過中間商提供溝通的橋樑,不用親力親爲,還能享受額外的服務,例如經紀人除了告訴明星關於導演廣告商提供的信息,還能提前篩選收集資料,分析可行性。中介除了可以給租客介紹房子帶看房,還能給介紹房子的相關朝向,過戶流程之類的額外服務。
其實總結下來就是兩點
1.不用爲一些事情投入過多精力,親力親爲
2.通過中間人,能獲得額外的服務和信息
轉到程序裏面就是
1.有業務需要調用外部接口,內部或者外部可能經常變更的時候,可以採用代理模式,內部業務不需要動,只改代理類就行了。這樣代碼無侵入性,無耦合
2.代理能在接口調用前後做額外工作,如記錄調用時間,次數,能數據校驗,攔截等
代理模式的實現方式
1.靜態代理
2.動態代理
a. cglib方式
b.jdk方式
靜態代理是比較容易理解的,其結構不外乎,創建一個類,裏面持有被用者的引用對象,並提供一個方法,供使用者調用。需要調用的時候,找代理類就行,你不用關心他怎麼實現的
動態代理其實就是靜態代理類,只不過是由jvm通過反射(asm)動態生成一個匿名代理類.class文件.接下來可以瞭解一下它們的使用與要點。
前置需求
有一個下班接口,有方法go,需要在下班前下班後做點事情
interface OffWork{
void go();
}
有一個實現類
public class Employee implements OffWork { public Employee(){} private String name; public Employee(String name) { this.name = name; } @Override public void go() { System.out.println("員工:"+name+"下班了"); } }
靜態代理
優點:結構簡單,實現也簡單,一目瞭然
缺點:項目中有大量代理類,結構一致,代碼重複,導致項目包文件過大。新增接口麻煩,接口變動代理類也得跟着變動,就算有工具可以生成代理類,也得替換和檢查
1 public class StaticProxy { 2 public StaticProxy(OffWork e){ 3 this.work=e; 4 } 5 private OffWork work; 6 public void go(){ 7 System.out.println("下班前準備,好像需要關電腦..."); 8 work.go(); 9 System.out.println("下班路上順便買個菜"); 10 } 11 }
動態代理-cglib方式
步驟:
1.創建代理類並實現MethodInterceptor 接口
2.持有調用者引用,並提供一個create方法,返回匿名代理類
3.在調用方法前後做額外工作before,after
1 public class CglibProxy implements MethodInterceptor { 2 private Object target; 3 4 public Object create(Object target) { 5 this.target = target; 6 Enhancer enhancer = new Enhancer(); 7 enhancer.setSuperclass(target.getClass()); 8 enhancer.setCallback(this); 9 return enhancer.create(); 10 } 11 12 @Override 13 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 14 before(); 15 Object res= method.invoke(target, objects); 16 after(); 17 return res; 18 } 19 private void before(){ 20 System.out.println("執行之前找點事情乾乾"); 21 } 22 private void after(){ 23 System.out.println("完成之後收工"); 24 } 25 }
注意!劃重點
以上cglib方式只能用於無參構造,。
下面是有參構造,注意他們之前的區別
1 public class CglibProxy implements MethodInterceptor { 2 private Object target; 3 4 public Object create(Object target) { 5 this.target = target; 6 Enhancer enhancer = new Enhancer(); 7 enhancer.setSuperclass(target.getClass()); 8 enhancer.setCallback(this); 9 return enhancer.create(); 10 } 11 public <T> T getInstance(T target,Class[] args,Object[] argsValue){ 12 Enhancer enhancer = new Enhancer(); 13 enhancer.setSuperclass(target.getClass()); 14 enhancer.setCallback(this); 15 return (T) enhancer.create(args,argsValue); 16 } 17 @Override 18 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 19 before(); 20 Object res= methodProxy.invokeSuper(o, objects); 21 /**被註釋的是無參構造用法**/ 22 // Object res=method.invoke(target, objects); 23 after(); 24 return res; 25 } 26 private void before(){ 27 System.out.println("執行之前找點事情乾乾"); 28 } 29 private void after(){ 30 System.out.println("完成之後收工"); 31 } 32 }
動態代理-jdk方式
在java.lang.reflect包下面InvocationHandler接口
步驟:
1.創建代理類並實現InvocationHandler接口
2.創建成員變量,用來存儲調用者的引用
3.提供create接口,通過方法注入,給成員變量賦值,返回匿名代理類
4.在invoke方法中調用接口,並添加額外業務
1 public class DynamicProxy implements InvocationHandler { 2 private Object target; 3 4 5 @Override 6 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 7 before(); 8 Object res= method.invoke(target,args); 9 after(); 10 return res; 11 } 12 public Object create(Object t ){ 13 target=t; 14 return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); 15 } 16 private void before(){ 17 System.out.println("執行之前找點事情乾乾"); 18 } 19 private void after(){ 20 System.out.println("完成之後收工"); 21 } 22 }
調用
1 public class MainTest { 2 public static void main(String[] args) { 3 /**靜態代理方式**/ 4 OffWork employee= new Employee("張一山"); 5 new StaticProxy(employee).go(); 6 System.out.println("=========================="); 7 /**jdk動態代理方式**/ 8 DynamicProxy dynamicProxy= new DynamicProxy(); 9 OffWork employee1=(OffWork) dynamicProxy.create(employee); 10 employee1.go(); 11 System.out.println("=========================="); 12 /**cglib動態代理方式**/ 13 CglibProxy cglibProxy=new CglibProxy(); 14 /**無參構造方式**/ 15 OffWork offWork= (OffWork)cglibProxy.create(employee); 16 offWork.go(); 17 System.out.println("=========================="); 18 /**有參構造方式**/ 19 OffWork s= cglibProxy.getInstance(employee,new Class[]{String.class},new Object[]{"張一山"}); 20 s.go(); 21 } 22 }
輸出結果
下班前準備,好像需要關電腦... 員工:張一山下班了 下班路上順便買個菜 ========================== 執行之前找點事情乾乾 員工:張一山下班了 完成之後收工 ========================== 執行之前找點事情乾乾 員工:null下班了 完成之後收工 ========================== 執行之前找點事情乾乾 員工:張一山下班了 完成之後收工
分析
我們常用的動態代理jdk方式和cglib 有什麼不同呢,如何選擇?
這就要聊到它們的實現方式上了。jdk動態代理和cglib動態代理。兩種方法的存在,各有各自的優勢。
jdk方式 InvocationHandler
jdk動態代理是由java內部的反射機制來實現的
特點:
jdk反射機制在生成類的過程中比較高效,在執行時效率較低
優點:無依賴,直接利用jdk反射
缺點:被代理的類必須實現接口方法,否則無法編譯通過,無法使用
cglib方式 MethodInterceptor
cglib動態代理底層則是藉助asm來實現。
特點:生成代理類的過程中比較慢,但生成後使用速度快
優點:
被代理類無需實現接口,普通類就可以了,沒有侷限性
缺點:有外部依賴,不過目前已經被封裝到spring-core.jar中
相比較來說,cglib的使用範圍更廣泛,更通用一點
總結
動態代理給我們程序添加了無數的可能,或者可以這樣說,動態編譯+反射+動態代理 給java程序帶來了很多奇思妙想,spring的核心也是在這裏,利用它實現了容器的加載,方法的增強,ioc 與aop 都是通過它們實現的。