黑马程序员——代理
?
?
?
?
分析代理類的作用與原理及AOP概念
代理的概念與作用
生活中的代理武漢人從武漢的代理商手中買聯想電腦和直接跑到北京傳智播客旁邊來找聯想總部買電腦,你覺得最終的主體業務目標有什么區別嗎?基本上一樣吧,都解決了核心問題,但是,一點區別都沒有嗎?從代理商那里買真的一點好處都沒有嗎?
程序中的代理
要為已存在的多個具有相同接口的目標類的各個方法增加一些系統功能,例如,異常處理、日志、計算方法的運行時間、事務管理、等等,你準備如何做?
編寫一個與目標類具有相同接口的代理類,代理類的每個方法調用目標類的相同方法,并在調用方法時加上系統功能的代碼。 如果采用工廠模式和配置文件的方式進行管理,則不需要修改客戶端程序,在配置文件中配置是使用目標類、還是代理類,這樣以后很容易切換,例如,想要日志功能時就配置代理類,否則配置目標類,這樣,增加系統功能很容易,以后運行一段時間后,又想去掉系統功能也很容易。
?
AOP(面向方面的編程)
系統中存在交叉業務,一個交叉業務就是要切入到系統中的一個方面,如下所示:
?
用具體的程序代碼描述交叉業務:
method1 ??????? method2??????? method3
{ ????????? { ????????? {
------------------------------------------------------切面
....? .... ??????? .....
------------------------------------------------------切面
}?????????? }????????? }
交叉業務的編程問題即為面向方面的編程(Aspect oriented program ,簡稱AOP),AOP的目標就是要使交叉業務模塊化。可以采
用將切面代碼移動到原始方法的周圍,這與直接在方法中編寫切面代碼的運行效果是一樣的,如下所示:
------------------------------------------------------切面
func1?? func2 func3
{?????? { ???? {
.... ???? ....? ...???? ...
} ???? } ????? }
------------------------------------------------------切面
使用代理技術正好可以解決這種問題,代理是實現AOP功能的核心和關鍵技術。
注意:
安全,事務,日志等功能要貫穿到好多個模塊中,所以,它們就是交叉業務。
重要原則:不要把供貨商暴露給你的客戶。
動態代理技術
要為系統中的各種接口的類增加代理功能,那將需要太多的代理類,全部采用靜態代理方
式,將是一件非常麻煩的事情!寫成百上千個代理類,是不是太累!
JVM可以在運行期動態生成出類的字節碼,這種動態生成的類往往被用作代理類,即動態代理類。
JVM生成的動態類必須實現一個或多個接口,所以,JVM生成的動態類只能用作具有相同接口的目標類的代理。
CGLIB庫可以動態生成一個類的子類,一個類的子類也可以用作該類的代理,所以,如果要為一個沒有實現接口的類生成動態代理類,那么可以使用CGLIB庫。
代理類的各個方法中通常除了要調用目標的相應方法和對外返回目標返回的結果外,還可以在代理方法中的如下四個位置加上系統功能代碼:
1、在調用目標方法之前
2、在調用目標方法之后
3、在調用目標方法前后
4、在處理目標方法異常的catch塊中
例子:
class proxyTest {
void sayHello(){
……….
try{
target.sayHello();
} catch(Exception e) {
………..
}
………….
}
}
創建動態類及查看其方法列表信息
創建實現了Collection接口的動態類和查看其名稱,分析Proxy.getProxyClass方法的各個參數。
編碼列出動態類中的所有構造方法和接收參數的類型。
編碼列出動態類中的所有方法和接收參數的類型。
package com.itheima.day03;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
public class ProxyTest {
public static void main(String[] args) {
/*
* 編碼列出動態類中的所有構造方法和接收參數的類型。
*/
Class clazzProxy =
Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
System.out.println(clazzProxy.getName());
System.out.println("--------begin constructors list-------");
Constructor[] constructors =clazzProxy.getConstructors();
for(Constructor constructor : constructors) {
String name = constructor.getName();
StringBuilder sb = new StringBuilder(name);
sb.append("(");
Class[] clazzParams =constructor.getParameterTypes();
for(ClassclazzParam : clazzParams) {
sb.append(clazzParam.getName()).append(",");
}
if(clazzParams !=null&& clazzParams.length != 0) {
sb.deleteCharAt(sb.length()-1);
}
sb.append(")");
System.out.println(sb.toString());
}
/*
* 編碼列出動態類中的所有方法和接收參數的類型。
* */
System.out.println("--------begin method list-------");
Method [] methods = clazzProxy.getMethods();
for(Method method : methods) {
String name = method.getName();
StringBuilder sb =new StringBuilder(name);
sb.append("(");
Class[] clazzParams =method.getParameterTypes();
for(Class clazzParam : clazzParams) {
sb.append(clazzParam.getName()).append(",");
}
if(clazzParams!=null&& clazzParams.length != 0) {
sb.deleteCharAt(sb.length()-1);
}
sb.append(")");
System.out.println(sb.toString());
}
}
}
?
運行結果:
com.sun.proxy.$Proxy0
--------beginconstructors list-------
com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
--------beginmethod list-------
add(java.lang.Object)
equals(java.lang.Object)
toString()
hashCode()
clear()
contains(java.lang.Object)
isEmpty()
addAll(java.util.Collection)
iterator()
size()
toArray([Ljava.lang.Object;)
toArray()
remove(java.lang.Object)
containsAll(java.util.Collection)
removeAll(java.util.Collection)
retainAll(java.util.Collection)
isProxyClass(java.lang.Class)
getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)
getInvocationHandler(java.lang.Object)
newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)
wait(long)
wait()
wait(long,int)
getClass()
notify()
notifyAll()
注意:StringBuilder與StringBuffer的區別?
答:StringBuffer是線程安全的,StringBuilder是線程不安全的。
StringBuilder在單線程的時候使用,StringBuffer則是在多線程的時候使用。
因此StringBuilder比StringBuffer效率高一些。
創建動態類的實例對象及調用其方法
步驟:
1、創建動態類的實例對象。通過反射獲得構造方法。
2、編寫一個最簡單的InvocationHandler類。
3、調用構造方法創建動態類的實例對象,并將編寫的InvocationHandler類的實例對象傳進去。
4、將創建動態類的實例對象的代理改成匿名內部類的形式編寫,鍛煉大家習慣匿名內部類。
package com.itheima.day03;
importjava.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
public class ProxyTest2 {
public static void main(String[] args)throws Exception {
Class<?> clazzProxy =Proxy.getProxyClass(
Collection.class.getClassLoader(),
Collection.class);
Collection<?> proxy =
(Collection<?>)clazzProxy.getConstructor(InvocationHandler.class).newInstance(new InvocationHandler(){
@Override
publicObject invoke(Object proxy,Method method,Object[] args)
throwsThrowable{
returnnull;
}
});
System.out.println(proxy);
//結果:null
proxy.clear();
//執行沒有返回值的方法,不會報告異常
proxy.size();
//執行有返回值的方法,會報告異常
}
}
完成InvocationHandler對象的內部功能
用Proxy.newInstance方法可以直接一步就創建出代理對象。
一個簡單的例子:
Foo f = (Foo)Proxy.newProxyInstance(Foo.class.getClassLoader(),new Class[] { Foo.class },
handler);
代碼示例:
package com.itheima.day03;
importjava.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
public class ProxyTest3 {
@SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception {
@SuppressWarnings("rawtypes")
Collection proxy = (Collection)Proxy.newProxyInstance(
Collection.class.getClassLoader(),
newClass[] {Collection.class },
newInvocationHandler() {
ArrayList<String> target =newArrayList<String>();
@Override
publicObject invoke(Object proxy,Method method,Object[] args) throws Throwable {
longstartTime = System.currentTimeMillis();
Object retVal = method.invoke(target, args);
longendTime =System.currentTimeMillis();
System.out.println(method.getName() +" 運行時間 "
+ (endTime - startTime) + "秒");
returnretVal;
}
});
proxy.add("黑馬程序員");
proxy.add("黑馬論壇");
proxy.add("CSDN社區");
System.out.println(proxy.size());
//結果:3
}
}
注意:
如果作為target的ArrayList對象放置在invoke方法內部定義,那么每次調用代理的某個方法,都會調用invoke方法,這樣作為target的ArrayList對象每次都會被創建,這樣就導致最后調用proxy.size()的時候,結果為0。
分析InvocationHandler對象的運行原理
動態生成的類實現了Collection接口(可以實現若干接口),生成的類有Collection接口中的所有方法和一個InvocationHandler參數的構造方法。
構造方法接受一個InvocationHandler對象,接受對象了要干什么用呢?該方法內部的代碼會是怎樣的呢?
答:接受的對象會通過構造函數賦值給某個成員變量。
$Proxy0 implementsCollection{
InvocationHandler handler;
public $Proxy0(InvocationHandler handler){
this.handler = handler;
}
}
實現Collection接口的動態類中的各個方法的代碼又是怎樣的呢?InvocationHandler接口中定義的invoke方法接受的
三個參數又是什么意思?圖解說明如下:
?
示例:
$Proxy0 implements Collection{
??? InvocationHandler handler;
??? public $Proxy0(InvocationHandler handler){
??????? this.handler = handler;
??? }
??? //生成的Collection接口中的方法的運行原理
??? int size(){
??????? return handler.invoke(this,this.getClass().getMethod("size"),null);
??? }
??? void clear(){
??????? handler.invoke(this,this.getClass().getMethod("clear"),null);
??? }
??? boolean add(Object obj){
??????? return handler.invoke(this,this.getClass().getMethod("add"),obj);
??? }
}
代碼示例:
package com.itheima.day03;
importjava.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
public class ProxyTest {
@SuppressWarnings("unchecked")
public static void main(String[] args)throws Exception {
@SuppressWarnings("rawtypes")
Collection proxy = (Collection)Proxy.newProxyInstance(
Collection.class.getClassLoader(),
newClass[] {Collection.class },
newInvocationHandler() {
@Override
publicObject invoke(Object proxy,Method method,
Object[] args)throws Throwable{
returnnull;
}
});
System.out.println(proxy.getClass());
// class com.sun.proxy.$Proxy0
System.out.println(proxy);
// 結果:null
proxy.clear();
// 執行沒有返回值的方法,不會報告異常
proxy.size();
//執行有返回值的方法,會報告異常
}
}
①分析上面打印動態類的實例對象時,結果為什么會是null呢?
答:打印動態類的示例對象實際上就是打印proxy的toString方法,也就是執行代理對象中的如下代碼:
String toString(){
return handler.invoke(this,this.getClass().getMethod("toString"),null);
}
由于invoke方法返回的是null,打印出來的結果肯定是null。
②調用有基本類型返回值的方法時為什么會出現NullPointerException異常?
答:執行proxy.size()方法,就是執行下面的代碼:
int size(){
return handler.invoke(this,this.getClass().getMethod("size"),null);
}
由于invoke方法返回的是null,要將null轉換為int類型,肯定會報告空指針異常。
③分析為什么動態類的實例對象的getClass()方法返回了正確結果呢?
答:調用代理對象的從Object類繼承的hashCode, equals, 或toString這幾個方法時,代理對象將調用請求轉發給InvocationHandler對象,對于其他方法,則不轉發調用請求,比如getClass方法,所以它會返回正確的結果。
總結分析動態代理類的設計原理與結構
?
?
?
注意:源對象的類必須自己定義時就實現接口,從該類的祖輩類上繼承的接口是無效的。
將創建代理的過程改為一種更優雅的方式,MyEclipse重構出一個getProxy方法綁定接收目標同時返回代理對象,讓
調用者更懶惰,更方便,調用者甚至不用接觸任何代理的API。
將系統功能代碼模塊化,即將切面代碼也改為通過參數形式提供,怎樣把要執行的系統功能代碼以參數形式提供?
把要執行的代碼裝到一個對象的某個方法里,然后把這個對象作為參數傳遞,接收者只要調用這個對象的方法,即
等于執行了外界提供的代碼!為方法增加一個Advice參數。
代碼示例:
package com.itheima.day03;
import java.lang.reflect.Method;
public interface Advice {
void startTime();
void endTime(Object target,Method method);
}
package com.itheima.day03;
import java.lang.reflect.Method;
public class MyAdvice implements Advice{
private long startTime;
@Override
public void startTime() {
startTime= System.currentTimeMillis();
}
@Override
public void endTime(Object target,Method method) {
longendTime =System.currentTimeMillis();
String objName = target.getClass().getName();
System.out.println("調用"+objName.substring(
(objName.lastIndexOf(".")+1),objName.length())+
"對象的"+method.getName()+"方法 耗時"+(endTime-startTime)+"秒");
}
}
package com.itheima.day03;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
public class ProxyTest {
@SuppressWarnings({ "rawtypes","unchecked" })
public static void main(String[] args) throws Exception {
finalArrayListtarget =newArrayList();
Collection collection = (Collection)getProxy(target,newMyAdvice());
collection.add("黑馬程序員");
}
private static Object getProxy(final Object target,final Advice advice) {
Object objProxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
newInvocationHandler() {
@Override
publicObject invoke(Object proxy,Method method,Object[] args) throws Throwabl{
advice.startTime();
Object retVal = method.invoke(target, args);
advice.endTime(target,method);
returnretVal;
}
});
returnobjProxy;
}
}
實現類似spring的可配置的AOP框架
工廠類BeanFactory負責創建目標類或代理類的實例對象,并通過配置文件實現切換。其getBean方法根據參數字符串返回一個相應的實例對象,如果參數字符串在配置文件中對應的類名不是ProxyFactoryBean,則直接返回該類的實例對象,否則,返回該類實例對象的getProxy方法返回的對象。
BeanFactory的構造方法接收代表配置文件的輸入流對象,配置文件格式如下:
#proxy=java.util.ArrayList
proxy=com.itheima.day03.ProxyFactoryBean
proxy.target=java.util.ArrayList
proxy.advice=com.itheima.day03.MyAdvice
ProxyFacotryBean充當封裝生成動態代理的工廠,需要為工廠類提供哪些配置參數信息?
目標
通知
編寫客戶端應用:
編寫實現Advice接口的類和在配置文件中進行配置
調用BeanFactory獲取對象。
代碼示例:
package com.itheima.day03;
import java.lang.reflect.Method;
public interface Advice {
??? void beforeAdvice();
??? void afterAdvice(Object target ,Method method);
}
package com.itheima.day03;
import java.lang.reflect.Method;
public class MyAdvice implements Advice{
??? private long startTime;
??? @Override
??? public void beforeAdvice() {
??????? startTime = System.currentTimeMillis();
??? }
??? @Override
??? public void afterAdvice(Object target ,Method method) {
??????? long endTime =System.currentTimeMillis();
??????? String objName = target.getClass().getName();
??????? System.out.println("調用"+objName.substring((objName.lastIndexOf(".")+1),objName.length())+
??????? "對象的"+method.getName()+"方法 耗時"+(endTime-startTime)+"秒");
??? }
}
package com.itheima.day03;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactoryBean {
??? private Advice advice;
??? private Object target;
??? public ProxyFactoryBean(){}
??? public Advice getAdvice() {
??????? return advice;
??? }
??? public Object getTarget() {
??????? return target;
??? }
??? public void setAdvice(Advice advice) {
??????? this.advice = advice;
??? }
??? public void setTarget(Object target) {
??? ??? this.target = target;
??? }
??? public Object getProxy(){
??????? Object objProxy =Proxy.newProxyInstance(
??????? target.getClass().getClassLoader(),
??????? target.getClass().getInterfaces(),
??????? new InvocationHandler() {
??????????? @Override
??????????? public Object invoke(Object proxy,Method method,Object[] args) throws Throwable {
??????????????? advice.beforeAdvice(target,method);
??????????????? Object retVal = method.invoke(target, args);
??????????????? advice.afterAdvice(target ,method);
??????????????? return retVal;
??????????? }
??????? });
??????? return objProxy;
??? }
}
package com.itheima.day03;
import java.io.InputStream;
import java.util.Properties;
public class BeanFactory {
??? private Properties prop = new Properties();
? ? public BeanFactory(InputStream ips) {
??????? try {
??????????? prop.load(ips);
??????? } catch (Exception e) {
??????????? e.printStackTrace();
??????? }
??? }
??? public Object getBean(String name) {
??????? String className = prop.getProperty(name);
????? Object bean =null;
??????? try {
??????????? Class<?> clazz =Class.forName(className);
??????????? bean = clazz.newInstance();
??????? } catch (Exception e) {
??????????? e.printStackTrace();
??????? }
??????? if(bean instanceofProxyFactoryBean) {
??????????? ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean) bean;
??????????? Object proxy = null;
??????????? try {
??????????????? Advice advice = (Advice)Class.forName(
??????????????? prop.getProperty(name + ".advice")).newInstance();
????????? ????? Object target =Class.forName(
??????????????? prop.getProperty(name + ".target")).newInstance();
??????????????? proxyFactoryBean.setAdvice(advice);
??????????????? proxyFactoryBean.setTarget(target);
??????????????? proxy = proxyFactoryBean.getProxy();
??????????? } catch (Exception e) {
??????????????? e.printStackTrace();
??????????? }
??????????? return proxy;
??????? }
??????? return bean;
??? }
}
package com.itheima.day03;
import java.io.InputStream;
public class AopFrameworkTest {
??? public static void main(String[] args)throws Exception {
??????? InputStream ips = AopFrameworkTest.class.getResourceAsStream("config.properties");
??????? Object objProxy = new BeanFactory(ips).getBean("proxy");
??????? System.out.println(objProxy.getClass().getName());
??? }
}
總結
- 上一篇: [html] 说说你对网格布局的理解
- 下一篇: [html] 如何禁止input输入的