本文主要介紹Java
中兩種常見的動態代理方式:JDK原生動態代理
和CGLIB動態代理
。java
就是爲其餘對象提供一種代理以控制對這個對象的訪問。代理能夠在不改動目標對象的基礎上,增長其餘額外的功能(擴展功能)。編程
代理模式角色分爲 3 種:dom
Subject
(抽象主題角色):定義代理類和真實主題的公共對外方法,也是代理類代理真實主題的方法;RealSubject
(真實主題角色):真正實現業務邏輯的類;Proxy
(代理主題角色):用來代理和封裝真實主題;若是根據字節碼的建立時機來分類,能夠分爲靜態代理和動態代理:ide
學習動態代理前,有必要來學習一下靜態代理。函數
靜態代理在使用時,須要定義接口或者父類,被代理對象(目標對象)與代理對象(Proxy)一塊兒實現相同的接口或者是繼承相同父類。性能
來看一個例子,模擬小貓走路的時間。學習
// 接口 public interface Walkable { void walk(); } // 實現類 public class Cat implements Walkable { @Override public void walk() { System.out.println("cat is walking..."); try { Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } } }
若是我想知道走路的時間怎麼辦?能夠將實現類Cat
修改成:測試
public class Cat implements Walkable { @Override public void walk() { long start = System.currentTimeMillis(); System.out.println("cat is walking..."); try { Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("walk time = " + (end - start)); } }
這裏已經侵入了源代碼,若是源代碼是不能改動的,這樣寫顯然是不行的,這裏能夠引入時間代理類CatTimeProxy
。ui
public class CatTimeProxy implements Walkable { private Walkable walkable; public CatTimeProxy(Walkable walkable) { this.walkable = walkable; } @Override public void walk() { long start = System.currentTimeMillis(); walkable.walk(); long end = System.currentTimeMillis(); System.out.println("Walk time = " + (end - start)); } }
若是這時候還要加上常見的日誌功能,咱們還須要建立一個日誌代理類CatLogProxy
。this
public class CatLogProxy implements Walkable { private Walkable walkable; public CatLogProxy(Walkable walkable) { this.walkable = walkable; } @Override public void walk() { System.out.println("Cat walk start..."); walkable.walk(); System.out.println("Cat walk end..."); } }
若是咱們須要先記錄日誌,再獲取行走時間,能夠在調用的地方這麼作:
public static void main(String[] args) { Cat cat = new Cat(); CatLogProxy p1 = new CatLogProxy(cat); CatTimeProxy p2 = new CatTimeProxy(p1); p2.walk(); }
這樣的話,計時是包括打日誌的時間的。
若是咱們須要計算SDK
中100個方法的運行時間,一樣的代碼至少須要重複100次,而且建立至少100個代理類。往小了說,若是Cat
類有多個方法,咱們須要知道其餘方法的運行時間,一樣的代碼也至少須要重複屢次。所以,靜態代理至少有如下兩個侷限性問題:
因此,咱們須要一個通用的代理類來代理全部的類的全部方法,這就須要用到動態代理技術。
學習任何一門技術,必定要問一問本身,這到底有什麼用。其實,在這篇文章的講解過程當中,咱們已經說出了它的主要用途。你發現沒,使用動態代理咱們竟然能夠在不改變源碼的狀況下,直接在方法中插入自定義邏輯。這有點不太符合咱們的一條線走到底的編程邏輯,這種編程模型有一個專業名稱叫AOP
。所謂的AOP
,就像刀同樣,抓住時機,趁機插入。
JDK實現代理只須要使用newProxyInstance方法,可是該方法須要接收三個參數:
@CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); final Class<?>[] intfs = interfaces.clone(); final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } /* * Look up or generate the designated proxy class. */ Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. */ try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); } return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } }
方法是在Proxy
類中是靜態方法,且接收的三個參數依次爲:
ClassLoader loader
//指定當前目標對象使用類加載器Class<?>[] interfaces
//目標對象實現的接口的類型,使用泛型方式確認類型InvocationHandler h
//事件處理器主要是完成InvocationHandler h
的編寫工做。
接口類UserService
:
public interface UserService { public void select(); public void update(); }
接口實現類,即要代理的類UserServiceImpl
:
public class UserServiceImpl implements UserService { @Override public void select() { System.out.println("查詢 selectById"); } @Override public void update() { System.out.println("更新 update"); } }
代理類UserServiceProxy
:
public class UserServiceProxy implements UserService { private UserService target; public UserServiceProxy(UserService target){ this.target = target; } @Override public void select() { before(); target.select(); after(); } @Override public void update() { before(); target.update(); after(); } private void before() { // 在執行方法以前執行 System.out.println(String.format("log start time [%s] ", new Date())); } private void after() { // 在執行方法以後執行 System.out.println(String.format("log end time [%s] ", new Date())); } }
主程序類:
public class UserServiceProxyJDKMain { public static void main(String[] args) { // 1. 建立被代理的對象,即UserService的實現類 UserServiceImpl userServiceImpl = new UserServiceImpl(); // 2. 獲取對應的classLoader ClassLoader classLoader = userServiceImpl.getClass().getClassLoader(); // 3. 獲取全部接口的Class, 這裏的userServiceImpl只實現了一個接口UserService, Class[] interfaces = userServiceImpl.getClass().getInterfaces(); // 4. 建立一個將傳給代理類的調用請求處理器,處理全部的代理對象上的方法調用 // 這裏建立的是一個自定義的日誌處理器,須傳入實際的執行對象 userServiceImpl InvocationHandler logHandler = new LogHandler(userServiceImpl); /* 5.根據上面提供的信息,建立代理對象 在這個過程當中, a.JDK會經過根據傳入的參數信息動態地在內存中建立和.class 文件等同的字節碼 b.而後根據相應的字節碼轉換成對應的class, c.而後調用newInstance()建立代理實例 */ // 會動態生成UserServiceProxy代理類,而且用代理對象實例化LogHandler,調用代理對象的.invoke()方法便可 UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler); // 調用代理的方法 proxy.select(); proxy.update(); // 生成class文件的名稱 ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceJDKProxy"); } }
這裏能夠保存下來代理生成的實現了接口的代理對象:
public class ProxyUtils { /* * 將根據類信息 動態生成的二進制字節碼保存到硬盤中, * 默認的是clazz目錄下 * params :clazz 須要生成動態代理類的類 * proxyName : 爲動態生成的代理類的名稱 */ public static void generateClassFile(Class clazz, String proxyName) { //根據類信息和提供的代理類名稱,生成字節碼 byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces()); String paths = clazz.getResource(".").getPath(); System.out.println(paths); FileOutputStream out = null; try { //保留到硬盤中 out = new FileOutputStream(paths + proxyName + ".class"); out.write(classFile); out.flush(); } catch (Exception e) { e.printStackTrace(); } finally { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } }
getProxyClass0()
生成代理類。JDK
生成的最終真正的代理類,它繼承自Proxy
並實現了咱們定義的接口.Proxy.newProxyInstance()
生成代理類的實例對象,建立對象時傳入InvocationHandler
類型的實例。InvocationHandler
類中的invoke()
方法。代理對象不須要實現接口,可是目標對象必定要實現接口,不然不能用動態代理
JDK
的動態代理機制只能代理實現了接口的類,而不能實現接口的類就不能實現JDK
的動態代理,cglib
是針對類來實現代理的,他的原理是對指定的目標類生成一個子類,並覆蓋其中方法實現加強,但由於採用的是繼承,因此不能對final
修飾的類進行代理。
Cglib
代理,也叫做子類代理,它是在內存中構建一個子類對象從而實現對目標對象功能的擴展。
Cglib
子類代理實現方法:
cglib
的jar
文件,可是Spring
的核心包中已經包括了Cglib
功能,因此直接引入Spring-core.jar
便可.final
,不然報錯final/static
,那麼就不會被攔截,即不會執行目標對象額外的業務方法.基本使用
<!-- https://mvnrepository.com/artifact/cglib/cglib --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2</version> </dependency>
方法攔截器
public class LogInterceptor implements MethodInterceptor{ /* * @param o 要進行加強的對象 * @param method 要攔截的方法 * @param objects 參數列表,基本數據類型須要傳入其包裝類 * @param methodProxy 對方法的代理, * @return 執行結果 * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { before(); Object result = methodProxy.invokeSuper(o, objects); after(); return result; } private void before() { System.out.println(String.format("log start time [%s] ", new Date())); } private void after() { System.out.println(String.format("log end time [%s] ", new Date())); } }
測試用例
這裏保存了代理類的.class
文件
public class CglibMain { public static void main(String[] args) { // 建立Enhancer對象,相似於JDK動態代理的Proxy類 Enhancer enhancer = new Enhancer(); // 設置目標類的字節碼文件 enhancer.setSuperclass(UserDao.class); // 設置回調函數 enhancer.setCallback(new LogInterceptor()); // create會建立代理類 UserDao userDao = (UserDao)enhancer.create(); userDao.update(); userDao.select(); } }
結果
log start time [Mon Nov 30 17:26:39 CST 2020] UserDao 更新 update log end time [Mon Nov 30 17:26:39 CST 2020] log start time [Mon Nov 30 17:26:39 CST 2020] UserDao 查詢 selectById log end time [Mon Nov 30 17:26:39 CST 2020]
JDK
動態代理只須要實現 InvocationHandler
接口,重寫 invoke
方法即可以完成代理的實現,Proxyxx.class
代理類字節碼,並生成對象extends
了Proxy
,而java是不容許多重繼承的,可是容許實現多個接口優勢:解決了靜態代理中冗餘的代理實現類問題。
缺點:JDK
動態代理是基於接口設計實現的,若是沒有接口,會拋異常。
JDK
動態代理限制了只能基於接口設計,而對於沒有接口的狀況,JDK
方式解決不了;CGLib
採用了很是底層的字節碼技術,其原理是經過字節碼技術爲一個類建立子類,並在子類中採用方法攔截的技術攔截全部父類方法的調用,順勢織入橫切邏輯,來完成動態代理的實現。MethodInterceptor
接口,重寫 intercept
方法,經過 Enhancer
類的回調方法來實現。CGLib
在建立代理對象時所花費的時間卻比JDK多得多,因此對於單例的對象,由於無需頻繁建立對象,用CGLib
合適,反之,使用JDK
方式要更爲合適一些。CGLib
因爲是採用動態建立子類的方法,對於final
方法,沒法進行代理。優勢:沒有接口也能實現動態代理,並且採用字節碼加強技術,性能也不錯。
缺點:技術實現相對難理解些。