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基礎詳解和源碼探究