1. 什么是動態代理
對象的執行方法,交給代理來負責。比如user.get() 方法,是User對象親自去執行。而使用代理則是由proxy去執行get方法。
舉例:投資商找明星拍廣告,投資商是通過經紀人聯系的,經紀人可以幫明星接這個廣告,也可以拒絕。做不做,怎么做都叫給經紀人和投資商談。
2. 實際場景應用
2.1 校驗用戶權限,每一個菜單請求,都要判斷一下請求的用戶是否有該菜單權限。菜單多了,代碼冗余,且容易遺漏。
通過動態代理就可以實現為:每一個用戶,每一個菜單的請求,都經過代理(proxy),由他判斷是否有權限,調用者只需要調用,實現自己的邏輯,不關心權限問題。
3. 動態代理完整案例:
/** * 創建用戶接口 */ public interface UserBean { String getUser(); }
import demo.knowledgepoints.section.inf.UserBean; public class UserBeanImpl implements UserBean { private String user = null; //flag:0 無權限,1有權限。 private String flag = null; public String getFlag() { return flag; } public void setFlag(String flag) { this.flag = flag; } public UserBeanImpl(String user,String flag) { this.user = user; this.flag = flag; } public String getUserName() { return user; } public String getUser() { System.out.println("this is getUser() method!"); return user; } public void setUser(String user) { this.user = user; System.out.println("this is setUser() method!"); } }
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class UserBeanProxy implements InvocationHandler { private Object targetObject; public UserBeanProxy(Object targetObject) { this.targetObject = targetObject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { UserBeanImpl userBean = (UserBeanImpl) targetObject; String flag = userBean.getFlag(); Object result = null; //權限判斷 if("1".equals(flag) ){ result = method.invoke(targetObject, args); }else{ System.out.println("sorry , You don't have permission"); } return result; } }
import demo.knowledgepoints.section.inf.UserBean; import java.lang.reflect.Proxy; public class TestSection { public static void main(String[] args) { UserBeanImpl targetObject = new UserBeanImpl("蕾蕾","1"); UserBeanProxy proxy = new UserBeanProxy(targetObject); //生成代理對象 UserBean object = (UserBean) Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), proxy); String userName = object.getUser(); System.out.println("userName: " + userName); } }
運行結果:
模擬動態代理自己實現一個動態代理的功能:https://www.cnblogs.com/jssj/p/12499086.html。這篇文章應該可以更好的讓人理解動態代理。
代理代理核心代碼
UserBean object = (UserBean) Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(), proxy);
public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
接口:InvocationHandler,代理需要實現該接口,並且實現方法:invoke。
方法:newProxyInstance原理分析:
先看源碼
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { //驗證傳入的InvocationHandler不能為空 Objects.requireNonNull(h); //復制代理類實現的所有接口 final Class<?>[] intfs = interfaces.clone(); //獲取安全管理器 final SecurityManager sm = System.getSecurityManager(); //進行一些權限檢驗 if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } //該方法先從緩存獲取代理類, 如果沒有再去生成一個代理類 Class<?> cl = getProxyClass0(loader, intfs); try { //進行一些權限檢驗 if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } //獲取參數類型是InvocationHandler.class的代理類構造器 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; } }); } //傳入InvocationHandler實例去構造一個代理類的實例 //所有代理類都繼承自Proxy, 因此這里會調用Proxy的構造器將InvocationHandler引用傳入 return cons.newInstance(new Object[]{h}); } catch (Exception e) { //統一用Exception捕獲了所有異常 throw new InternalError(e.toString(), e); } }
可以看到,newProxyInstance方法首先是對參數進行一些權限校驗,之后通過調用getProxyClass0方法生成了代理類的類對象,然后獲取參數類型是InvocationHandler.class的代理類構造器。檢驗構造器是否可以訪問,最后傳入InvocationHandler實例的引用去構造出一個代理類實例,InvocationHandler實例的引用其實是Proxy持有着,因為生成的代理類默認繼承自Proxy,所以最后會調用Proxy的構造器將引用傳入。在這里我們重點關注getProxyClass0這個方法,看看代理類的Class對象是怎樣來的,下面貼上該方法的代碼
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { //目標類實現的接口不能大於65535 if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } //獲取代理類使用了緩存機制 return proxyClassCache.get(loader, interfaces); }
可以看到getProxyClass0方法內部沒有多少內容,首先是檢查目標代理類實現的接口不能大於65535這個數,之后是通過類加載器和接口集合去緩存里面獲取,如果能找到代理類就直接返回,否則就會調用ProxyClassFactory這個工廠去生成一個代理類。
//代理類生成工廠 private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> { //代理類名稱前綴 private static final String proxyClassNamePrefix = "$Proxy"; //用原子類來生成代理類的序號, 以此來確定唯一的代理類 private static final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class<?> intf : interfaces) { //這里遍歷interfaces數組進行驗證, 主要做三件事情 //1.intf是否可以由指定的類加載進行加載 //2.intf是否是一個接口 //3.intf在數組中是否有重復 } //生成代理類的包名 String proxyPkg = null; //生成代理類的訪問標志, 默認是public final的 int accessFlags = Modifier.PUBLIC | Modifier.FINAL; for (Class<?> intf : interfaces) { //獲取接口的訪問標志 int flags = intf.getModifiers(); //如果接口的訪問標志不是public, 那么生成代理類的包名和接口包名相同 if (!Modifier.isPublic(flags)) { //生成的代理類的訪問標志設置為final accessFlags = Modifier.FINAL; //獲取接口全限定名, 例如:java.util.Collection String name = intf.getName(); int n = name.lastIndexOf('.'); //剪裁后得到包名:java.util String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); //生成的代理類的包名和接口包名是一樣的 if (proxyPkg == null) { proxyPkg = pkg; } else if (!pkg.equals(proxyPkg)) { //代理類如果實現不同包的接口, 並且接口都不是public的, 那么就會在這里報錯 throw new IllegalArgumentException( "non-public interfaces from different packages"); } } } //如果接口訪問標志都是public的話, 那生成的代理類都放到默認的包下:com.sun.proxy if (proxyPkg == null) { proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } //生成代理類的序號 long num = nextUniqueNumber.getAndIncrement(); //生成代理類的全限定名, 包名+前綴+序號, 例如:com.sun.proxy.$Proxy0 String proxyName = proxyPkg + proxyClassNamePrefix + num; //這里是核心, 用ProxyGenerator來生成字節碼, 該類放在sun.misc包下 byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags); try { //根據二進制文件生成相應的Class實例 return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); } } }
該工廠的apply方法會被調用用來生成代理類的Class對象,由於代碼的注釋比較詳細,我們只挑關鍵點進行闡述,其他的就不反復贅述了。
1. 在代碼中可以看到JDK生成的代理類的類名是“$Proxy”+序號。
2. 如果接口是public的,代理類默認是public final的,並且生成的代理類默認放到com.sun.proxy這個包下。
3. 如果接口是非public的,那么代理類也是非public的,並且生成的代理類會放在對應接口所在的包下。
4. 如果接口是非public的,並且這些接口不在同一個包下,那么就會報錯。
參考: