代理(proxy)分為2種: 靜態代理 動態代理 動態代理常用的有jdk動態代理、cglib代理。 靜態代理 1、新建User介面 2、新建實現類UserImpl 3、新建代理類UserProxy,也實現User介面,對目標對象(的方法)進行增強 4、使用代理。新建測試類Test 靜態代理的特點 代 ...
代理(proxy)分為2種:
- 靜態代理
- 動態代理 動態代理常用的有jdk動態代理、cglib代理。
靜態代理
1、新建User介面
1 public interface User { 2 void addUser(); 3 void deleteUser(); 4 void updateUser(); 5 }
2、新建實現類UserImpl
1 public class UserImpl implements User { 2 @Override 3 public void addUser() { 4 //模擬添加用戶 5 System.out.println("正在添加用戶"); 6 System.out.println("已添加用戶"); 7 } 8 9 @Override 10 public void deleteUser() { 11 //模擬刪除用戶 12 System.out.println("正在刪除用戶"); 13 System.out.println("已刪除用戶"); 14 } 15 16 @Override 17 public void updateUser() { 18 //模擬修改用戶信息 19 System.out.println("正在修改用戶信息"); 20 System.out.println("已修改用戶信息"); 21 } 22 }
3、新建代理類UserProxy,也實現User介面,對目標對象(的方法)進行增強
1 public class UserProxy implements User { 2 //定義目標對象(要代理的對象) 3 private User user; 4 5 //構造函數,初始化目標對象 6 public UserProxy(User user){ 7 this.user=user; 8 } 9 10 @Override 11 public void addUser() { 12 System.out.println("開啟事務..."); //前處理,模擬開啟事務 13 user.addUser(); //調用目標對象相應的方法 14 System.out.println("提交事務..."); //後處理,模擬提交事務 15 } 16 17 @Override 18 public void deleteUser() { 19 System.out.println("開啟事務..."); 20 user.deleteUser(); 21 System.out.println("提交事務..."); 22 } 23 24 @Override 25 public void updateUser() { 26 System.out.println("開啟事務..."); 27 user.updateUser(); 28 System.out.println("提交事務..."); 29 } 30 }
4、使用代理。新建測試類Test
1 public class Test { 2 public static void main(String[] args){ 3 UserImpl user = new UserImpl(); //目標對象 4 UserProxy userProxy = new UserProxy(user); //代理 5 userProxy.addUser(); //調用代理類的方法 6 } 7 }
靜態代理的特點
- 代理類、目標類需要實現同一介面
- 編譯時就已確定目標對象的類型(編譯時類型,比如上例中編譯時就知道目標對象的類型是User)
- 目標對象只能必須是特定類型,比如上例中目標對象只能是User類型,UserProxy這個代理類只能代理User的實例。如果要代理其他類型的對象,需要再寫代理類,這就很麻煩了,一種代理只能代理一種類型,太雞肋了,我們一般不用靜態代理。
因為目標對象的類型是固定的,靜止不動的(靜態的),所以這種代理方式叫做靜態代理。
何謂代理?
代理是使用一個更強大的類(在原類的基礎上進行功能擴展)來代替原來的類進行工作。
比如我在使用UserImpl類時,還想使用事務、記錄日誌等做一些其他的操作,這些操作不屬於用戶類的範疇,不能封裝到UserImpl類中。這時就可以使用代理來對原來的類(方法)進行增強。代理類保留了原有類的所有功能,在此基礎上擴展了其他功能,更加強大。
被代理的類(UserImpl類)叫做目標類,實現了代理的類(UserProxy類)叫做代理類。
JDK動態代理
1、新建介面User
2、新建實現類UserImpl
3、新建類ProxyFactory,用於創建代理對象
1 class ProxyFactory{ 2 //目標對象。動態代理是你傳給它什麼目標對象,它就代理什麼對象,能代理所有類型的對象,所以聲明為Object 3 private Object target; 4 5 //構造器,初始化目標對象 6 public ProxyFactory(Object target) { 7 this.target = target; 8 } 9 10 //獲取代理對象。目標對象是Object,所以代理對象也是Object 11 public Object getProxyInstance(){ 12 ClassLoader classLoader = target.getClass().getClassLoader(); //獲取目標類的類載入器。可通過目標對象獲取,也可通過目標類獲取,但目標對象聲明為Object,所以只能通過目標對象來獲取(不知道目標類) 13 Class<?>[] interfaces = target.getClass().getInterfaces(); //獲取目標類所實現的所有介面的Class對象。因為目標類可能實現了多個介面,所以用的是複數。 14 InvocationHandler invocationHandler = new InvocationHandler() { //創建InvocationHandler介面的實例。這裡使用匿名內部類來創建 15 @Override 16 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 17 System.out.println("前增強..."); //此處寫前增強的代碼。 18 Object returnValue=method.invoke(target,args); //調用目標方法 19 System.out.println("後增強..."); //此處寫後增強的代碼 20 return returnValue; 21 } 22 }; 23 24 25 Object proxyInstance = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler); //創建代理對象,參數就是上面三個參數 26 return proxyInstance; //返回代理對象 27 } 28 }
4、新建測試類Test
1 public class Test { 2 public static void main(String[] args){ 3 UserImpl user = new UserImpl(); //目標對象 4 Object obj = new ProxyFactory(user).getProxyInstance(); //創建代理對象 5 User userProxy=(User)obj; //返回值是Object,需要強轉。只能強轉為目標類所實現的介面類型,如果強轉為目標類的類型則代理失敗(報錯) 6 userProxy.addUser(); //通過代理對象來調用方法 7 } 8 }
我們看下實現 InvocationHandler介面中的 這段代碼:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("前增強..."); //此處寫前增強的代碼 Object returnValue=method.invoke(target,args); //調用目標方法 System.out.println("後增強..."); //此處寫後增強的代碼 return returnValue; }
invocation是調用的意思,invoke也是調用的意思。InvocationHandler介面是用來指定如何調用目標類中的方法,按照寫的實現來調用。
invoke這個函數就是反射中調用指定方法的函數。
java.lang.reflect中調用某個類中的方法: Object invoke(Object proxy, Method method, Object[] args)
第一個參數指定目標對象,第二個參數指定要調用的方法,第三個參數是所調方法需要的實參值,參數個數不確定,可寫為數組形式。invoke()的返回值就是所調方法的返回值,只不過聲明為了Object。
通過代理對象調用方法: userProxy.addUser();
會自動把目標對象、要調用的方法、實參表向下傳遞給invoke(),invoke執行完以後自動把返回值向上傳回來。
jdk動態代理,是指用jdk自帶的東西(反射)來實現動態代理,所以又叫java動態代理,不是說要代理jdk。
前後增強的代碼可以放在函數中,然後在invoke()中調用對應的函數。
JDK動態代理的特點
- 目標類必須繼承介面,創建代理的類無需繼承介面
- 可以代理任何類型的類。傳什麼類型的目標類,就代理什麼類型,所有叫做動態代理。
- 所設置的前後增強,是所有目標類中所有方法都會添加(執行)的。靜態代理可以一個方法一個方法地設置,可以根據需要具體設置每一個方法的增強。jdk動態代理是一棍子打死,全都使用相同的前後增強。
CGLIB動態代理
靜態代理、jdk動態代理都要求目標類必須實現一個或多個介面,如果目標類沒有實現介面,則代理失敗。
cglib代理不要求目標類實現介面。
1、如果是單獨使用cglib,需要導入cglib.jar包以及cglib的依賴包asm.jar,不推薦,這樣導包很容易出問題(cglib、asm的版本要對應)。
如果使用maven,則會自動導入依賴的asm.jar。
如果使用了spring,在spring的核心包spring-core.jar中已經內嵌了cglib,不必再導包。
2、新建類User,不必實現任何介面
3、新建創建代理對象的類ProxyFactory,需實現MethodInterceptor介面
1 class ProxyFactory implements MethodInterceptor { 2 //目標對象。聲明為Object 3 private Object target; 4 5 //構造器,初始化目標對象 6 public ProxyFactory(Object target) { 7 this.target = target; 8 } 9 10 //給目標對象創建一個代理對象 11 public Object getProxyInstance(){ 12 Enhancer en = new Enhancer(); //創建工具類對象 13 en.setSuperclass(target.getClass()); //設置父類 14 en.setCallback(this); //設置回調函數。這句代碼是給代理類對象設置攔截intercept()。前面只是繼承了目標類,此處設置攔截(在攔截中實現增強、調用目標方法) 15 return en.create(); //創建代理對象並返回 16 17 } 18 19 //攔截 20 @Override //cglib代理仍是在反射的基礎上擴展而來的,所以參數和反射調用方法的參數差不多目標對象、背調方法、實參表、方法代理 21 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 22 System.out.println("前增強..."); //前增強代碼 23 Object returnValue = method.invoke(target, objects); //傳入目標對象、實參表。 24 System.out.println("後增強..."); //後增強代碼 25 return returnValue; //返回被調函數的返回值,是作為Object返回的 26 } 27 28 }
4、新建測試類Test
1 public class Test { 2 public static void main(String[] args){ 3 User user = new User(); //目標對象 4 Object obj = new ProxyFactory(user).getProxyInstance(); //創建代理對象 5 User userProxy=(User) obj; //返回值是Object,需要強轉為目標類對象。 6 userProxy.addUser(); //通過代理對象來調用方法 7 } 8 }
CGLIB動態代理的特點
- 可以代理任何類,但目標類不能用final修飾,因為代理類要繼承目標類;目標類中的方法不能使用final(要改寫,前後增強)、static(要求是實例方法)修飾。
- 目標類可以實現介面,也可以不實現任何介面。
- 目標類中的所有方法都會被增強
如果目標類實現了介面,使用jdk、cglib都行;如果目標類沒有實現任何介面,則使用cglib。