---------- android培訓、java培訓、期待與您交流! ----------
一、“代理概述”及“AOP概念”
?。ㄒ唬┐砀攀?/strong>
1、問題:要為已存在的多個具有相同接口的目標類的各個方法增加一些系統功能,例如,異常處理、日志、計算方法的運行時間、事務管理等等,如何去做?
解答:編寫一個與目標類具有相同接口的代理類,代理類的每個方法調用目標類的相同方法,并在調用方法時加上系統功能的代碼。
2、代理原理圖,如下:
3、代理的優點
如果采用工廠模式和配置文件的方式進行管理,則不需要修改客戶端程序,在配置文件中配置是使用目標類、還是代理類,這樣以后很容易切換。例如,想要日志功能時就配置代理類,否則配置目標類,這樣,增加系統功能很容易,以后運行一段時間后,又想去掉系統功能也很容易。
?。ǘ〢OP概念
1、問題引入:
?。?)系統中存在交叉業務,一個交叉業務就是要切入到系統中的一個方面,如下所示:
安全 事務 日志
StudentService ———|——————|——————|—————
CourseService ———|——————|——————|—————
MiscService ———|——————|——————|—————
(2)用具體的程序代碼描述交叉業務:
method1 method2 method3
{ { {
------------------------------------------切面
.... .... ......
------------------------------------------切面
} } }
2、AOP概念
?。?)定義:交叉業務的編程問題即為面向方面的編程(aspect oriented PRogram ,簡稱AOP),AOP的目標就是要使交叉業務模塊化。
(2)可以采用將切面代碼移動到原始方法的周圍,這與直接在方法中編寫切面代碼的運行效果是一樣的,如下所示:
----------------------------------------切面
func1 func2 func3
{ { {
.... .... ....
} } }
-----------------------------------------切面
總結:(1)使用代理技術正好可以解決這種交叉業務模塊化的問題,代理是實現AOP功能的核心和關鍵技術。(2)安全,事務,日志等功能要貫穿到好多個模塊中,所以,它們就是交叉業務。
二、動態代理技術
1、手動增加代理類存在的問題?
要為系統中的各種接口的類增加代理功能,則需要太多的代理類,全部采用靜態代理方式,就要寫成百上千個代理類,將是一件工作量巨大且非常麻煩的事情。
2、如何解決上述存在的問題?
JVM可以在“運行期”動態生成出類的字節碼,這種動態生成的類往往被用作代理類,即動態代理類。
3、動態類需注意細節:
JVM生成的動態類必須實現一個或多個接口,這樣JVM就知道該實現什么方法。所以,JVM生成的動態類只能用作具有相同接口的目標類的代理。
4、如果一個目標類自身沒有實現接口,如何讓JVM動態生成的代理類與目標類有相同的方法列表呢?
生成的代理類的方法聲明要不要和目標類的方法一樣?要。但目標類自身并沒有實現接口,那通過什么樣的方式告訴JVM生成的代理類與目標類有相同的方法列表,JVM干不了這件事情,因為沒接口。
這時候有一個第三方CGLIB庫,CGLIB庫可以動態生成一個類的子類,一個類的子類也可以用作該類的代理,所以,如果要為一個沒有實現接口的類生成動態代理類,那么可以使用CGLIB庫。
5、在代理方法中什么位置可以插入系統功能代碼?
代理類的各個方法中通常除了要調用目標的相應方法和對外返回目標返回的結果外,還可以在代理方法中的如下四個位置加上系統功能代碼:
?。?)在調用目標方法之前
?。?)在調用目標方法之后
?。?)在調用目標方法前后
(4)在處理目標方法異常的catch塊中
三、JVM動態生成的類
(一)創建動態類及查看其方法列表信息
1、要求:
?。?)創建實現了Collection接口的動態類和查看其名稱,分析Proxy.getProxyClass方法的各個參數。
?。?)編碼列出動態類中的所有構造方法和參數簽名
?。?)編碼列出動態類中的所有方法和參數簽名
2、示例代碼:
1 import java.lang.reflect.Constructor; 2 import java.lang.reflect.Method; 3 import java.lang.reflect.Proxy; 4 import java.util.Collection; 5 public class ProxyTest { 6 /** 7 * @param args 8 * @throws SecurityException 9 * @throws NoSuchMethodException10 * @throws Exception11 * @throws IllegalArgumentException12 * @throws IllegalaccessException13 * @throws InstantiationException14 */15 public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, Exception {16 Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);17 System.out.println(clazzProxy1.getName());18 19 System.out.println("------------begin constructor list ------------");20 Constructor[] constructors = clazzProxy1.getConstructors();21 for(Constructor constructor : constructors){22 String name = constructor.getName();23 StringBuilder sb = new StringBuilder(name);24 sb.append("(");25 Class[] clazzParams = constructor.getParameterTypes();26 for(Class clazzParam : clazzParams ){27 sb.append(clazzParam.getName()).append(",");28 }29 if( clazzParams!=null && clazzParams.length!=0)30 sb.deleteCharAt(sb.length()-1);31 sb.append(")");32 System.out.println(sb);33 }34 35 System.out.println("------------begin method list -------------");36 Method[] methods = clazzProxy1.getMethods();37 for(Method method : methods){38 String name = method.getName();39 StringBuilder sb = new StringBuilder(name);40 sb.append("(");41 Class[] clazzParams = method.getParameterTypes();42 for(Class clazzParam : clazzParams ){43 sb.append(clazzParam.getName()).append(",");44 }45 if( clazzParams!=null && clazzParams.length!=0)46 sb.deleteCharAt(sb.length()-1);47 sb.append(")");48 System.out.println(sb);49 }50 }51 }
?。ǘ﹦摻▌討B類的實例對象及調用其方法
1、創建動態類的實例對象有三種方式:
?。?)首先通過Proxy類的getProxyClass(ClassLoader loader, Class<?>... interfaces)方法,獲取代理類的對象;然后通過反射獲得構造方法;最后通過接口InvocationHandler的子類創建對象;
(2)首先通過Proxy類的getProxyClass(ClassLoader loader, Class<?>... interfaces)方法,獲取代理類的對象;通過反射獲得構造方法;通過給構造方法的newInstance()傳入InvocationHandler的匿名內部類來創建對象;
?。?)直接通過Proxy類的newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法,創建對象。
2、創建動態類的實例對象的代碼實現
1 import java.lang.reflect.Constructor; 2 import java.lang.reflect.InvocationHandler; 3 import java.lang.reflect.Method; 4 import java.lang.reflect.Proxy; 5 import java.util.ArrayList; 6 import java.util.Collection; 7 public class ProxyTest { 8 /** 9 * @param args10 * @throws SecurityException11 * @throws NoSuchMethodException12 * @throws Exception13 * @throws IllegalArgumentException14 * @throws IllegalAccessException15 * @throws InstantiationException16 */17 public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, Exception {18 Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);19 Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);20 21 System.out.println("------------begin create instance list --------------");22 //System.out.println("------------方式一:創建動態類的實例對象 ----------");23 class MyInvocationHandler1 implements InvocationHandler{24 @Override25 public Object invoke(Object proxy, Method method, Object[] args)26 throws Throwable {27 // TODO Auto-generated method stub28 return null;29 }30 }31 Collection proxy1 = (Collection)constructor.newInstance(new MyInvocationHandler1());32 System.out.println(proxy1);33 34 // System.out.println("------------方式二:創建動態類的實例對象 -----------");35 Collection proxy2 = (Collection)constructor.newInstance(new InvocationHandler(){36 @Override37 public Object invoke(Object proxy, Method method, Object[] args)38 throws Throwable {39 // TODO Auto-generated method stub40 return null;41 }42 });43 44 // System.out.println("------------方式三:創建動態類的實例對象 ------------");45 Collection proxy3 = (Collection)Proxy.newProxyInstance(46 Collection.class.getClassLoader(),47 new Class[]{Collection.class},48 new InvocationHandler(){49 ArrayList target = new ArrayList();50 @Override51 public Object invoke(Object proxy, Method method,52 Object[] args) throws Throwable {53 long beginTime = System.currentTimeMillis();54 Object retVal = method.invoke(target, args);55 long endTime = System.currentTimeMillis();56 System.out.println(method.getName()+"run time"+(endTime-beginTime));57 return retVal;58 }59 }60 );61 proxy3.add("zxx");62 proxy3.add("flx");63 proxy3.add("lhm");64 proxy3.add("bxd");65 proxy3.add("yzz"); 66 System.out.println(proxy3.size());67 }68 }
?。ㄈ┛偨Y思考
問題:讓JVM創建動態類及其實例對象,需要給它提供哪些信息?
解答:主要包括三個方面的信息:
(1)生成的類中有哪些方法,通過讓其實現哪些接口的方式進行告知;
?。?)產生的類字節碼必須有個一個關聯的類加載器對象;
?。?)生成的類中的方法的代碼是怎樣的,也得由我們提供。把我們的代碼寫在一個約定好了接口對象的方法中,把對象傳給它,它調用我的方法,即相當于插入了我的代碼。提供執行代碼的對象就是那個InvocationHandler對象,它是在創建動態類的實例對象的構造方法時傳遞進去的。在上面的InvocationHandler對象的invoke方法中加一點代碼,就可以看到這些代碼被調用運行了。
* 用Proxy.newInstance方法直接一步就創建出代理對象。
四、動態生成的類的內部代碼分析
在上面“創建動態類的實例對象”的代碼中,動態生成的類實現了Collection接口(可以實現若干接口),生成的類有Collection接口中的所有方法和一個如下接受InvocationHandler參數的構造方法。
1、問題:構造方法接受一個InvocationHandler對象,接收這個對象要干什么用呢?該方法內部的代碼是怎樣的呢?
?。?)構造方法接收一個參數,為了記住這個參數,以后運用它。
?。?)內部代碼:
1 $Proxy0 implements Collection{2 InvocationHandler handler;3 public $Proxy0(InvocationHandler handler){4 this.handler = handler;5 }6 }
2、問題:實現Collection接口的動態類中的各個方法的代碼又是怎樣的呢? InvocationHandler接口中定義的invoke方法接收的三個參數又是什么意思?
1 (1)$Proxy0 implements Collection{ 2 InvocationHandler handler; 3 public $Proxy0(InvocationHandler handler){ 4 this.handler = handler; 5 } 6 //生成的Collection接口中的方法的運行原理 7 int size(){ 8 return handler.invoke(this, this.getClass().getMethod("size"), null); 9 }10 void clear(){11 handler.invoke(this, this.getClass().getMethod("clear"), null);12 }13 boolean add(Object obj){14 handler.invoke(this, this.getClass().getMethod("add"), obj);15 }16 }
?。?)InvocationHandler接口中定義的invoke方法接收的三個參數意義,如下圖說明:
說明:客戶端調用了代理對象objProxy,調用了代理對象的add()方法,為該方法傳遞了字符串參數"abc"。
3、為什么動態類的實例對象的getClass()方法返回了正確結果呢?
調用調用代理對象的從Object類繼承的hashCode, equals, 或toString這幾個方法時,代理對象將調用請求轉發給InvocationHandler對象,對于其他方法,則不轉發調用請求。
五、動態生成的類成為目標類的代理
1、動態代理的工作原理圖
2、eclipse重構出一個getProxy方法綁定接收目標同時返回代理對象,怎樣將目標類作為參數傳進去?
?。?)直接在InvocationHandler實現類中創建目標類的實例對象,可以看運行效果和加入日志代碼,但沒有實際意義。
?。?)為InvocationHandler實現類注入目標類的實例對象,不能采用匿名內部類的形式了。
?。?)讓匿名的InvocationHandler實現類訪問外面方法中的目標類實例對象的final類型的引用變量。
3、在上面將目標類作為參數傳入之后,將系統功能代碼模塊化,即將切面代碼也改為通過參數形式提供,怎樣把要執行的系統功能代碼以參數形式提供?
(1)把要執行的代碼裝到一個對象的某個方法里,然后把這個對象作為參數傳遞,接收者只要調用這個對象的方法,即等于執行了外界提供的代碼!
?。?)為bind方法增加一個Advice參數。
4、將目標類和系統功能作為參數傳遞給getProxy()方法,實現示例代碼如下:
?。?)創建ProxyTest類
1 import java.lang.reflect.Constructor; 2 import java.lang.reflect.InvocationHandler; 3 import java.lang.reflect.Method; 4 import java.lang.reflect.Proxy; 5 import java.util.ArrayList; 6 import java.util.Collection; 7 public class ProxyTest { 8 /** @param args 9 * @throws SecurityException10 * @throws NoSuchMethodException11 * @throws Exception 12 * @throws IllegalArgumentException13 * @throws IllegalAccessException14 * @throws InstantiationException15 */16 public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, Exception {17 final ArrayList target = new ArrayList();//將目標抽取出來,方法里面的內部類要訪問局部變量必須添加final關鍵字 18 Collection proxy3 = (Collection)getProxy(target,new MyAdvice());//抽取出來的方法19 proxy3.add("zxx");20 proxy3.add("flx");21 proxy3.add("lhm"); 22 System.out.println(proxy3.size());23 }24 private static Object getProxy(final Object target,final Advice advice) { /*做成通用的方法,返回Object*/25 Object proxy3 = Proxy.newProxyInstance(26 /*Collection.class.getClassLoader(), //第一個參數*/27 target.getClass().getClassLoader(), // 代理類的類加載器與目標類的類加載器相同,與目標類有關。28 29 /*new Class[]{Collection.class}, //第二個參數*/30 target.getClass().getInterfaces(), //與target實現相同的接口,代理類要實現的接口也是目標類實現的接口,與目標類有關31 32 new InvocationHandler(){ //第三個參數,33 @Override34 public Object invoke(Object proxy, Method method,35 Object[] args) throws Throwable {36 /*37 long beginTime = System.currentTimeMillis(); //將系統功能抽取為一個對象38 Object retVal = method.invoke(target, args);39 long endTime = System.currentTimeMillis(); //將系統功能抽取為一個對象 40 System.out.println(method.getName()+"run time"+(endTime-beginTime));41 return retVal;42 */43 advice.beforeMethod(method);44 Object retVal = method.invoke(target, args);45 advice.afterMethod(method); 46 return retVal;47 }48 }49 );50 return proxy3;51 }52 }
?。?)創建Advice接口
import java.lang.reflect.Method;public interface Advice { //一般來說,這個建議的接口應該有四個方法,這四個方法可以分別插入: /* 代理類的各個方法中通常除了要調用目標的相應方法和對外返回目標返回的結果外, * 還可以在代理方法中的如下四個位置加上系統功能代碼: * 1.在調用目標方法之前 * 2.在調用目標方法之后 * 3.在調用目標方法前后 * 4.在處理目標方法異常的catch塊中 * */ void beforeMethod(Method method); void afterMethod(Method method); }
?。?)創建Advice接口的子類MyAdvice
1 import java.lang.reflect.Method; 2 public class MyAdvice implements Advice { 3 long beginTime = 0; 4 public void beforeMethod(Method method) { 5 // TODO Auto-generated method stub 6 System.out.println("到黑馬程序員訓練營來學習了!"); 7 beginTime = System.currentTimeMillis(); //將系統功能抽取為一個對象 8 } 9 10 public void afterMethod(Method method) {11 // TODO Auto-generated method stub12 System.out.println("從黑馬程序員訓練營畢業工作了!");13 long endTime = System.currentTimeMillis(); //將系統功能抽取為一個對象 14 System.out.println(method.getName()+" method run of time "+(endTime-beginTime));15 System.out.print(System.lineSeparator() );16 }17 }
六、實現類似spring的可配置的AOP框架
(一)工廠類BeanFactory
1、工廠類BeanFactory:負責創建目標類或代理類的實例對象,并通過配置文件實現切換。
2、getBean方法:根據參數字符串返回一個相應的實例對象。如果參數字符串在配置文件中對應的類名不是ProxyFactoryBean,則直接返回該類的實例對象,否則返回該類示例對象的getProxy方法返回的對象。
3、BeanFactory的構造方法:接收代表配置文件的輸入流對象的配置文件。
4、ProxyFactoryBean為BeanFactory提供配置參數信息:(1)目標(target)(2)通告(advice)
5、BeanFactory和ProxyFactoryBean:
(1)BeanFactory是一個純粹的bean工程,就是創建bean即相應的對象的工廠。
?。?)ProxyfactoryBean是BeanFactory中的一個特殊的Bean,是創建代理的工廠。
?。ǘ崿F類似spring的可配置的AOP框架的思路:
1、創建BeanFactory類:
?。?)構造方法:接受一個配置文件,通過Properties對象加載InputStream流對象。
?。?)創建getBean(String name)方法:根據類名name,拿到對應的類名className。
(3)根據類名獲取其字節碼對象,并創建實例對象bean。
?。?)判斷bean是否是特殊的Bean即ProxyFactoryBean。
?、?如果是,就要創建代理類,并設置目標(target)和通告(advice),分別得到各自的實例對象,并返回代理類實例對象。
?、?如果不是在返回Bean對象自己。
2、創建ProxyFactoryBean類,定義target和advice;定義getProxy()方法,用于創建代理類對象。
3、創建配置文件config.properties,對配置文件進行配置,配置內容如下
xxx=java.util.ArrayList
#xxx=cn.itheima.day3.aopframework.ProxyFactoryBean
xxx.advice=cn.itheima.day3.MyAdvice
xxx.target=java.util.ArrayList
注: #表示注釋當前行。
4、作一個測試類:AopFrameworkTest進行測試。
?。ㄈ┩暾a示例
1、創建BeanFactory類:
package cn.itheima.day3.aopframework;import java.io.IOException;import java.io.InputStream;import java.util.Properties;import cn.itheima.day3.Advice;public class BeanFactory { Properties props = new Properties(); public BeanFactory(InputStream ips){ try { props.load(ips); } catch (IOException e) { e.printStackTrace(); } } public Object getBean(String name){ String className = props.getProperty(name);//根據類名name,拿到對應的類名。 Object bean = null; try { Class clazz = Class.forName(className); bean = clazz.newInstance(); } catch (Exception e) { e.printStackTrace(); } if(bean instanceof ProxyFactoryBean){ Object proxy = null; ProxyFactoryBean ProxyFactoryBean = (ProxyFactoryBean)bean; try { Advice advice = (Advice)Class.forName(props.getProperty(name+".advice")).newInstance(); Object target = Class.forName(props.getProperty(name+".target")).newInstance(); ProxyFactoryBean.setAdvice(advice); ProxyFactoryBean.setTarget(target); proxy = ProxyFactoryBean.getProxy(); } catch (Exception e) { e.printStackTrace(); } return proxy; } return bean; }}
2、創建ProxyFactoryBean類:
1 package cn.itheima.day3.aopframework; 2 3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Method; 5 import java.lang.reflect.Proxy; 6 import cn.itheima.day3.Advice; 7 public class ProxyFactoryBean { 8 private Advice advice; 9 private Object target;10 public Advice getAdvice() {11 return advice;12 }13 public void setAdvice(Advice advice) {14 this.advice = advice;15 }16 public Object getTarget() {17 return target;18 }19 public void setTarget(Object target) {20 this.target = target;21 }22 public Object getProxy() {23 Object proxy3 = Proxy.newProxyInstance( 24 target.getClass().getClassLoader(), // 代理類的類加載器與目標類的類加載器相同,與目標類有關。25 target.getClass().getInterfaces(), //與target實現相同的接口,代理類要實現的接口也是目標類實現的接口,與目標類有關26 27 new InvocationHandler(){ //第三個參數28 @Override29 public Object invoke(Object proxy, Method method,30 Object[] args) throws Throwable {31 advice.beforeMethod(method);32 Object retVal = method.invoke(target, args);33 advice.afterMethod(method);34 return retVal;35 }36 }37 );38 return proxy3;39 }40 }
3、創建配置文件config.properties。
xxx=java.util.ArrayList#xxx=cn.itheima.day3.aopframework.ProxyFactoryBeanxxx.advice=cn.itheima.day3.MyAdvicexxx.target=java.util.ArrayList
4、創建AopFrameworkTest測試類,進行測試:
1 package cn.itheima.day3.aopframework; 2 3 import java.io.InputStream; 4 public class AopFrameworkTest { 5 /** 6 * @param args 7 */ 8 public static void main(String[] args) { 9 // TODO Auto-generated method stub10 InputStream ips = AopFrameworkTest.class.getResourceAsStream("config.properties");11 Object bean = new BeanFactory(ips).getBean("xxx");12 System.out.println(bean.getClass().getName());13 }14 }
5、設計到的接口Advice及其子類MyAdvice
?。?)Advice接口:
1 package cn.itheima.day3;2 import java.lang.reflect.Method;3 public interface Advice { 4 void beforeMethod(Method method);5 void afterMethod(Method method);6 }
?。?)MyAdvice類:
1 package cn.itheima.day3; 2 import java.lang.reflect.Method; 3 public class MyAdvice implements Advice { 4 long beginTime = 0; 5 public void beforeMethod(Method method) { 6 // TODO Auto-generated method stub 7 System.out.println("到黑馬程序員訓練營來學習了!"); 8 beginTime = System.currentTimeMillis(); //將系統功能抽取為一個對象 9 }10 11 public void afterMethod(Method method) {12 // TODO Auto-generated method stub13 System.out.println("從黑馬程序員訓練營畢業工作了!");14 long endTime = System.currentTimeMillis(); //將系統功能抽取為一個對象 15 System.out.println(method.getName()+" method run of time "+(endTime-beginTime));16 System.out.print(System.lineSeparator() );17 }18 }
---------- android培訓、java培訓、期待與您交流! ----------
新聞熱點
疑難解答