1.什麼是代理?
對類或物件(目標物件)進行增強功能,最終形成一個新的代理物件,(Spring Framework中)當應用呼叫該物件(目標物件)的方法時,實際呼叫的是代理物件增強後的方法,比如對功能方法login實現日誌記錄,可以通過代理實現;
PS:目標物件--被增強的物件;代理物件--增強後的物件;
2.為什麼需要代理?
一些類裡面的方法有相同的程式碼或類中有相同的功能,可以將這些相同抽取出來形成一個公共的方法或功能,但Java有兩個重要的原則:單一職責(對類來說的,即一個類應該只負責一項職責)和開閉原則(開放擴充套件,修改關閉),如果每個類的每個功能都呼叫了公共功能,就破壞了單一職責,如下圖;如果這個類是別人已經寫好的,你動了這個程式碼,同時也破壞了開閉原則(同時改動程式碼很麻煩,裡面可能涉及其他很多的呼叫,可能帶出無數的bug,改程式碼比新開發功能還難/(ㄒoㄒ)/~~);
由於有上面的問題存在,使用代理來實現是最好的解決辦法;
3.Java實現代理有哪些?
(1)靜態代理:通過對目標方法進行繼承或聚合(介面)實現;(會產生類爆炸,因此在不確定的情況下,儘量不要使用靜態代理,避免產生類爆炸)
1)繼承:代理物件繼承目標物件,重寫需要增強的方法;
//業務類(目標物件) public class UserServiceImpl { public void query(){ System.out.println("業務操作查詢資料庫...."); } } //日誌功能類 public class Log { public static void info(){ System.out.println("日誌功能"); } } //繼承實現代理(代理物件) public class UserServiceLogImpl extends UserServiceImpl { public void query(){ Log.info(); super.query(); } }
從上面程式碼可以看出,每種增強方法會產生一個代理類,如果現在增強方法有日誌和許可權,單個方法增強那需要兩個代理類(日誌代理類和許可權代理類),如果代理類要同時擁有日誌和許可權功能,那又會產生一個代理類,同時由於順序的不同,可能會產生多個類,比如先日誌後許可權是一個代理類,先許可權後日志又是另外一個代理類。
由此可以看出代理使用繼承的缺陷:產生的代理類過多(產生類爆炸),非常複雜,維護難;
2)聚合:代理物件和目標物件都實現同一介面,使用裝飾者模式,提供一個代理類構造方法(代理物件當中要包含目標物件),引數是介面,重寫目標方法;
//介面 public interface UserDao { void query(); } //介面實現類:目標物件 public class UserDaoImpl implements UserDao { @Override public void query() { System.out.println("query......"); } } //日誌功能類 public class Log { public static void info(){ System.out.println("日誌功能"); } } //聚合實現代理:同樣實現介面,使用裝飾者模式;(代理物件) public class UserDaoLogProxy implements UserDao { UserDao userDao; public UserDaoLogProxy(UserDao userDao){//代理物件包含目標物件 this.userDao = userDao; } @Override public void query() { Log.info(); userDao.query(); } } //測試 public static void main(String[] args) { UserDaoImpl target = new UserDaoImpl(); UserDaoLogProxy proxy = new UserDaoLogProxy(target); proxy.query(); }
聚合由於利用了面向介面程式設計的特性,產生的代理類相對繼承要少一點(雖然也是會產生類爆炸,假設有多個Dao,每個Dao產生一個代理類,所以還是會產生類爆炸),如下案例:
//時間記錄功能 public class Timer { public static void timer(){ System.out.println("時間記錄功能"); } } //代理類:時間功能+業務 public class UserDaoTimerProxy implements UserDao { UserDao userDao; public UserDaoTimerProxy(UserDao userDao){ this.userDao = userDao; } @Override public void query() { Timer.timer(); userDao.query(); } } //測試 public static void main(String[] args) { //timer+query UserDao target = new UserDaoTimerProxy(new UserDaoImpl()); //log+timer+query UserDao proxy = new UserDaoLogProxy(target); proxy.query(); } public static void main(String[] args) { //log+query UserDao target = new UserDaoLogProxy(new UserDaoImpl()); //timer+log+query UserDao proxy = new UserDaoTimerProxy(target); proxy.query(); }
PS:
裝飾和代理的區別:代理不需要指定目標物件,可以對任何物件進行代理;裝飾需要指定目標物件,所以需要構造方法引數或set方法指定目標物件;
幾個io的Buffer流(BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter)就是使用了裝飾模式的靜態代理;
public class BufferedReader extends Reader { private Reader in; ........ public BufferedReader(Reader in, int sz) { super(in); if (sz <= 0) throw new IllegalArgumentException("Buffer size <= 0"); this.in = in; cb = new char[sz]; nextChar = nChars = 0; } public BufferedReader(Reader in) { this(in, defaultCharBufferSize); } }
(2)動態代理:Java有JDK動態代理和CGLIB代理;(Spring Framework通過Spring AOP實現代理,底層還是使用JDK代理和CGLIB代理來實現)
模擬動態代理:不需要手動建立類檔案(因為一旦手動建立類檔案,會產生類爆炸),通過介面反射生成一個類檔案,然後呼叫第三方的編譯技術,動態編譯這個產生的類檔案成class檔案,然後利用URLclassLoader把這個動態編譯的類載入到jvm中,然後通過反射把這個類例項化。(通過字串產生一個物件實現代理);
PS:Java檔案 -> class檔案 -> byte位元組(JVM)-> class物件(類物件)-> new(物件);
所以步驟如下:
1)程式碼實現一個內容(完整的Java檔案內容,包含包名、變數、構造方法、增強後的方法等),使其通過IO產生一個Java檔案;
2)通過第三方編譯技術產生一個class檔案;
3)載入class檔案利用反射例項化一個代理物件出來;
public class ProxyUtil { /** * content --->string * | * |生成 * v * .java <-----通過io產生 * .class <-----Java檔案程式設計產生 * * .new <-----class檔案反射產生例項物件 * @return */ public static Object newInstance(Object target){ Object proxy=null; //根據物件獲取介面 Class targetInf =target.getClass().getInterfaces()[0]; //獲取介面的所有方法 //getMethods(),該方法是獲取本類以及父類或者父介面中所有的公共方法(public修飾符修飾的) //getDeclaredMethods(),該方法是獲取本類中的所有方法,包括私有的(private、protected、預設以及public)的方法 Method[] declaredMethods = targetInf.getDeclaredMethods(); String line="\n"; String tab ="\t"; //介面名稱 String targetInfName = targetInf.getSimpleName(); String content =""; //包位置 String packageContent = "package com;"+line; //介面 String importContent = "import "+targetInf.getName()+";"+line; String clazzFirstLineContent = "public class $Proxy implements "+targetInfName+"{"+line; //屬性 String filedContent =tab+"private "+targetInfName+" target;"+line; //構造方法 String constructorContent =tab+"public $Proxy ("+targetInfName+" target){" +line +tab+tab+"this.target =target;" +line+tab+"}"+line; //方法 String methodContent = ""; for(Method method:declaredMethods){ //返回值 String returnTypeName = method.getReturnType().getSimpleName(); //方法名 String methodName = method.getName(); // Sting.class String.class 引數型別 Class<?>[] parameterTypes = method.getParameterTypes(); String argsContent = ""; String paramsContent=""; int flag = 0; for(Class args :parameterTypes){ //String,引數型別 String simpleName = args.getSimpleName(); //String p0,Sting p1, argsContent+=simpleName+" p"+flag+","; paramsContent+="p"+flag+","; flag++; } if (argsContent.length()>0){ argsContent=argsContent.substring(0,argsContent.lastIndexOf(",")-1); paramsContent=paramsContent.substring(0,paramsContent.lastIndexOf(",")-1); } methodContent+=tab+"public "+returnTypeName+" "+methodName+"("+argsContent+") {"+line //增強方法先寫死 +tab+tab+"System.out.println(\"log\");"+line; if(returnTypeName.equals("void")){ methodContent+=tab+tab+"target."+methodName+"("+paramsContent+");"+line +tab+"}"+line; }else{ methodContent+=tab+tab+"return target."+methodName+"("+paramsContent+");"+line +tab+"}"+line; } } content+=packageContent+importContent+clazzFirstLineContent+filedContent +constructorContent+methodContent+"}"; //生成Java檔案 File file = new File("D:\\com\\$Proxy.java"); try{ if(!file.exists()){ file.createNewFile(); } //建立 FileWriter wr = new FileWriter(file); wr.write(content); wr.flush(); wr.close(); //編譯Java檔案 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null); Iterable units = fileMgr.getJavaFileObjects(file); JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units); t.call(); fileMgr.close(); //new --> 反射 URL[] urls = new URL[]{new URL("file:D:\\\\")}; //載入外部檔案 URLClassLoader classLoader = new URLClassLoader(urls); Class<?> proxyClass = classLoader.loadClass("com.$Proxy"); Constructor constructor = proxyClass.getConstructor(targetInf); //構造方法建立例項 proxy = constructor.newInstance(target); //clazz.newInstance();//根據預設構造方法建立物件 //Class.forName() }catch (Exception e){ e.printStackTrace(); } return proxy; } }
自定義代理的缺點:生成Java檔案;動態編譯檔案;需要一個URLClassLoader;涉及到了IO操作,軟體的最終效能體現到了IO操作,即IO操作影響到軟體的效能;
案例:
1)介面:
public interface UserDao { void query(); void query(String name); String getName(String id); }
2)實現類:
public class UserService implements UserDao { @Override public void query() { System.out.println("query"); } @Override public void query(String name) { System.out.println(name); } @Override public String getName(String id) { System.out.println("id:"+id); return "李四"; } }
3)測試:
public static void main(String[] args) { UserDao dao = (UserDao) ProxyUtil.newInstance(new UserService()); dao.query(); dao.query("張三"); } --------結果-------- log query log 張三
package com; import com.hrh.dynamicProxy.dao.UserDao; public class $Proxy implements UserDao{//自定義動態代理生成的檔案 private UserDao target; public $Proxy (UserDao target){ this.target =target; } public String getName(String p) { System.out.println("log"); return target.getName(p); } public void query(String p) { System.out.println("log"); target.query(p); } public void query() { System.out.println("log"); target.query(); } }
JDK動態代理:基於反射實現,通過介面反射得到位元組碼,然後將位元組碼轉成class,通過一個native(JVM實現)方法來執行;
案例實現:實現 InvocationHandler 介面重寫 invoke 方法,其中包含一個物件變數和提供一個包含物件的構造方法;
public class MyInvocationHandler implements InvocationHandler { Object target;//目標物件 public MyInvocationHandler(Object target){ this.target=target; } /** * @param proxy 代理物件 * @param method 目標物件的目標方法 * @param args 目標方法的引數 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("log"); return method.invoke(target,args); } }
public class MyInvocationHandlerTest { public static void main(String[] args) { //引數: 當前類的classLoader(保證MyInvocationHandlerTest當前類可用) // 介面陣列:通過介面反射得到介面裡面的方法,對介面裡面的所有方法都進行代理 // 實現的InvocationHandler:引數是目標物件 UserDao jdkproxy = (UserDao) Proxy.newProxyInstance(MyInvocationHandlerTest.class.getClassLoader(), new Class[]{UserDao.class},new MyInvocationHandler(new UserService())); jdkproxy.query("query"); //-----結果------ //log //query } }
下面檢視底層JDK生成的代理類class:
byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy18", new Class[]{UserDao.class}); try { FileOutputStream fileOutputStream = new FileOutputStream("xxx本地路徑\\$Proxy18.class"); fileOutputStream.write(bytes); fileOutputStream.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
或在程式碼的最前面新增下面程式碼:
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "xxx本地路徑");
代理類class反編譯後的內容:下面的內容驗證了JDK動態代理為什麼基於聚合(介面)來的,而不能基於繼承來的?因為JDK動態代理底層已經繼承了Proxy,而Java是單繼承,不支援多繼承,所以以介面來實現;
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // import com.hrh.dao.UserDao; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy18 extends Proxy implements UserDao { private static Method m1; private static Method m4; private static Method m5; private static Method m2; private static Method m0; private static Method m3; public $Proxy18(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final void query(String var1) throws { try { super.h.invoke(this, m4, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final void query() throws { try { super.h.invoke(this, m5, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String getName(String var1) throws { try { return (String)super.h.invoke(this, m3, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m4 = Class.forName("com.hrh.dao.UserDao").getMethod("query", Class.forName("java.lang.String")); m5 = Class.forName("com.hrh.dao.UserDao").getMethod("query"); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); m3 = Class.forName("com.hrh.dao.UserDao").getMethod("getName", Class.forName("java.lang.String")); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
CGLIB代理:藉助asm(一個操作位元組碼的框架)實現代理操作;CGLIB基於繼承來的(前文有CGLIB代理類class的反編譯可以看出);
public class UserService { public void query(){ System.out.println("query"); } public static void main(String[] args) { // 通過CGLIB動態代理獲取代理物件的過程 Enhancer enhancer = new Enhancer(); // 設定enhancer物件的父類 enhancer.setSuperclass(UserService.class); // 設定enhancer的回撥物件 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("before method run..."); Object result = proxy.invokeSuper(obj, args); System.out.println("after method run..."); return result; } }); //建立代理物件 UserService bean = (UserService) enhancer.create(); bean.query(); } }
//-----------結果------
before method run...
query
after method run...
PS:如果只是對專案中一個類進行代理,可以使用靜態代理,如果是多個則使用動態代理;
關於代理的其他相關知識介紹可參考前文:Spring(11) - Introductions進行類擴充套件方法、Spring筆記(3) - SpringAOP基礎詳解和原始碼探究