代理
用代碼來簡單看看代理模式
有個類
class A{
void sayHello(){
System.out.println("we like ZhangXiaoXiang teacher");----------------------位置1
}
}
現在我想在位置1的代碼打印輸出前或後,打印點其他的比如System.out.println("we like HeiMaXuenLianYing!");,但是這時候我沒有源文件(.java文件)改不了源碼,我隻能調用A類中的sayHello方法,那怎麼辦呢?
這時候我們就可以用代理模式帶實現這一需求,重新定義一個類(A對象的代理類)
package shipin49;
/**
* A類的代理
*
* @author Terry
* @date 2014-5-29
*
*/
public class AProxy {
public void sayHello(){
A a = new A();
//附加的代碼
System.out.println("we like HeiMaXuenLianYing1!");
a.sayHello();
System.out.println("we like HeiMaXuenLianYing2!");
}
}
在main方法中運行如下代碼
public static void main(String[] args) {
// TODO Auto-generated method stub
//客戶端不直接找被代理的對象(廠家),而是找代理商
AProxy aproxy = new AProxy();
aproxy.sayHello();
//直接找廠家失去了代理意義
System.out.println("--------------------------------");
A a = new A();
a.sayHello();
}
輸出結果
we like HeiMaXuenLianYing1!
we like ZhangXiaoXiang teacher
we like HeiMaXuenLianYing2!
--------------------------------
we like ZhangXiaoXiang teacher
感覺代理模式和裝飾者模式很相像。裝飾者模式重點在於添加附加行為修飾被裝飾者,而代理模式的重點則是代替本人作業,減少對實際對象的操作。
AOP(麵向切麵編程)
什麼是AOP?
1、AOP
Aspect(方麵 )Oriented(導向)Programming(程序設計):意為:麵向切麵(行為)編程,通過預編譯方式和運行期動態代理來實現程序功能的統一維護的一種技術。是函數式編程的一種衍生泛型。AOP在其他領域有其他含義。它是Spring框架中的一個重要內容。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。[1]
2、函數式編程(直白的說函數式編程就是方法編程----這是我的理解)
函數式編程是種編程典範,它將電腦運算視為函數的計算。函數編程語言最重要的基礎是 λ 演算(lambda calculus)。而且λ演算的函數可以接受函數當作輸入(參數)和輸出(返回值)。和指令式編程相比,函數式編程強調函數的計算比指令的執行重要。和過程化編程相比,函數式編程裏,函數的計算可隨時調用。
3、更加詳細的AOP參照https://baike.baidu.com/subview/73626/13548606.htm?fr=aladdin(百度百科)
張孝祥老師視頻中的AOP解說
●係統中存在著許多交叉業務,一個交叉業務就是要切入到係統中的一個方麵,如下所示
安全 事物 日誌
StudentService-----------------------------|------------------------|--------------------------|-----------------------------
管理學生信息的類(管理學生模塊) | | |
CourseService------------------------------|------------------------|--------------------------|-----------------------------
管理課程信息的類 (管理課程模塊) | | |
ClassroomService--------------------------|------------------------|------------------------- |-----------------------------
管理教室信息的類(管理教室模塊)
上麵的這3個類,他們各管各的事,互不往來,但是他們都要有安全,都要處理事物,都要寫日誌,而安全、事物、日誌都貫穿到了許多個模塊當中,於是這3個功能就成了模塊的交叉業務 。它們就是交叉業務,穿插到了許多的類當中。
●用具體的程序代碼描述交叉業務:
method1 (模塊1) method2 (模塊2) method3(模塊3)
{ { {
-------------------------------------------------------------------------------------------------------切麵 (在方法的這個位置,都要他們有日誌功能或安全功能亦或其他的功能)
............. .............. ............
-------------------------------------------------------------------------------------------------------切麵
} } }
●交叉業務的編程問題即為麵向方麵的編程,AOP的目標就是要使交叉業務模塊化。可以采用將切麵代碼移動到原始方法的周圍,這與直接在方法中編寫切麵代碼的運行效果是一樣的,如下所示:
-------------------------------------------------------------------------------------------------------切麵 (在方法的這個位置,都要他們有日誌功能或安全功能亦或其他的功能)
method1 (模塊1) method2 (模塊2) method3(模塊3)
{ { {
............. .............. ............
} } }
-------------------------------------------------------------------------------------------------------切麵 (在方法的這個位置,都要他們有日誌功能或安全功能亦或其他的功能)
●使用代理技術正好可以解決這種問題,代理是實現AOP功能的核心和關鍵技術。
動態代理技術
1、
要為係統中的各種接口增加代理功能,如果用靜態代理,那將需要再增加和係統中的接口一樣多的代理類,那樣工程量很大,也太麻煩。
JVM可以在運行期動態生成出類的字節碼,這種動態生成的類往往被用做代理,既動態代理。
JVM生成的動態類必須實現一個或多個接口,所以,JVM生成的動態類隻能用作具有相同接口的目標類代理。(如果不實現一個或多個接口,那JVM生成的動態類就沒有方法,一個沒有方法的類那來做什麼呢?這樣意義不大。)
如果有一個類它沒有實現任何接口,那此時要動態的生成此類的代理,但是JVM生成的又必須要實現一個或多個接口,這時候怎麼辦呢?可以使用第三方的CGLIB庫來動態生成此類的代理。
代理的內容可以放在目標方法的什麼位置呢?
void proxyMothod(){
位置1----------------在調用目標方法之前
try{
targetMothod();
}catch(Exception e){
位置2-----------------在調用目標方法異常的cath語句中
}
位置3----------------在調用目標方法之後
}
打印動態代理生成的字節碼中的方法
public static void main(String[] args) {
// TODO Auto-generated method stub
//一般情況下類加載器都是用和接口一樣的,但是你可以用其他的類加載器類,clazzProxy1動態代理加載的類
Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
System.out.println("字節碼名字:"+clazzProxy1.getName());
//獲得動態代理生成的類的構造方法
Constructor[] constructors = clazzProxy1.getConstructors();
for (Constructor constructor : constructors) {
StringBuilder sb = new StringBuilder();
sb.append("構造方法:"+constructor.getName()+"(");
for (Class clazz : constructor.getParameterTypes()) {
sb.append(clazz.getName().substring(clazz.getName().lastIndexOf(".")+1)+",");
}
sb.deleteCharAt(sb.lastIndexOf(","));
sb.append(");");
System.out.println(sb);
}
/**
* 打印其他方法
*/
for (Method constructor : clazzProxy1.getMethods()) {
StringBuilder sb = new StringBuilder();
sb.append("其他方法:"+constructor.getName()+"(");
for (Class clazz : constructor.getParameterTypes()) {
sb.append(clazz.getName().substring(clazz.getName().lastIndexOf(".")+1)+",");
}
if(sb.lastIndexOf(",")!=-1){
sb.deleteCharAt(sb.lastIndexOf(","));
}
sb.append(");");
System.out.println(sb);
}
}
運行輸出:
$Proxy0
構造方法:$Proxy0(InvocationHandler);
其他方法:add(Object);
其他方法:equals(Object);
其他方法:toString();
..........
2、創建動態類的實例對象
問題一:
public static void main(String[] args) throws Exception {
Class clazz = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
clazz.newInstance();
}
上麵的代碼可以運行嗎?
答:不可以,因為clazz沒有無參的構造函數方法。
示例代碼:
/**
* 內部類
* Invocation(調用)Handler(處理程序)
* @author Terry
* @date 2014-5-29
*
*/
class MyInvocationHander1 implements InvocationHandler{
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
return null;-------------------------------------------------------------------------3
}
}
//獲得動態代理的字節碼,Collection是個接口
Class clazz = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
//獲得構造函數,不能使用clazz.newInstance()因為clazz沒有無參的構造函數
Constructor con = clazz.getConstructor(InvocationHandler.class);
//用構造函數創建一個新的對象new MyInvocationHander1()自定義的InvocationHandler
Collection collection = (Collection)con.newInstance(new MyInvocationHander1());
Collection test = null;
System.out.println(collection);//這裏的結果為空,但是有2中情況,一種是對象為空,一種是toString()的返回值為空
System.out.println(test);
// System.out.println(collection.toString());-------------------------------------1
// System.out.println(test.toString());---------------------------------------------2
在main方法中運行輸出:
null
null
將位置1的代碼取消注釋運行輸出:
null
null
null
將位置2的代碼取消注釋運行報空指針異常
注銷位置2的代碼,更改位置3的代碼為:return "wahaha";運行輸出:
wahaha
null
wahaha
判斷對象是否為空System.out.println(collection==null);運行輸出:false;
匿名內部類的寫法:
//new InvocationHandler();new InvocationHandler(){},大括號的意義,不是很懂
Collection collection2 = (Collection)con.newInstance(new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
return null;
}
});
Proxy另一種newProxyInstance方法
static Object |
newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 返回一個指定接口的代理類實例,該接口可以將方法調用指派到指定的調用處理程序。 |
這方法和上麵的示例代碼的效果是一樣的。
方法一、
public void dynamicProxy(){
Object object = Proxy.newProxyInstance(System.class.getClassLoader(), new Class[]{Collection.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
return null;
}
});
printMethodName(object.getClass());
}
方法二、
/**
* 打印一個類中的所有方法
* @param clazz1
*/
public static void printMethodName(Class clazz1){
for (Method constructor : clazz1.getMethods()) {
StringBuilder sb = new StringBuilder();
sb.append("其他方法:"+constructor.getName()+"(");
for (Class clazz : constructor.getParameterTypes()) {
sb.append(clazz.getName().substring(clazz.getName().lastIndexOf(".")+1)+",");
}
if(sb.lastIndexOf(",")!=-1){
sb.deleteCharAt(sb.lastIndexOf(","));
}
sb.append(");");
System.out.println(sb);
}
}
方法三、
在main方法中調用:
ProxyTest pt = new ProxyTest();
pt.dynamicProxy();
運行輸出:
其他方法:add(Object);
其他方法:equals(Object);
其他方法:toString();
其他方法:hashCode();
其他方法:clear();
...............
問題二、Object object = Proxy.newProxyInstance(System.class.getClassLoader(), new Class[]{Collection.class,Date.class}, new InvocationHandler()...將代碼改成這個程序報錯,原因仔細看了幫助文檔後明白了。
示例代碼:
public void dynamicProxy(){
Collection collectionProxy = (Collection)Proxy.newProxyInstance(
System.class.getClassLoader(),
new Class[]{Runnable.class,Comparable.class,Collection.class},
new InvocationHandler() {
ArrayList target = new ArrayList();//被代理的對象,Collection間接的實現類
@Override
/**
*
* @param proxy:代理對象(JVM動態生成的)
* @param method:被調用的方法
* @param args:方法中的參數
* @return :返回方法的返回值
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
System.out.println("=====================================================");
// ArrayList target = new ArrayList();//被代理的對象,Collection間接的實現類
// System.out.println(proxy);
System.out.println(method.getName());
if(args!=null){
for (Object object : args) {
System.out.println(object);
}
}
//在調用目標之前做點什麼
System.out.println("附加的內容1");
Object objct = method.invoke(target, args);
//在調用目標之後做點什
System.out.println("附加的內容2");
return objct;
}
});
// System.out.println(object);
// printMethodName(object.getClass());
collectionProxy.add("zxx");
collectionProxy.add("lhm");
collectionProxy.remove("lhm");
collectionProxy.add(new ArrayList().add(new ArrayList().add("hahahaha")));-----------------------------------這裏輸出true的原因是,方法添加成功所以為true
System.out.println("大小是多少:" + collectionProxy.size());
}
在main方法中運行:
ProxyTest pt = new ProxyTest();
pt.dynamicProxy();輸出結果:
=====================================================
add
zxx
附加的內容1
附加的內容2
=====================================================
add
lhm
附加的內容1
附加的內容2
=====================================================
remove
lhm
附加的內容1
附加的內容2
=====================================================
add
true
附加的內容1
附加的內容2
=====================================================
size
附加的內容1
附加的內容2
大小是多少:2
自己寫的一個小型的通用性動態代理框架
接口
/**
* 功能程序的接口
*
* @author Terry
* @date 2014-5-30
*
*/
public interface Advice {
public void before();
public void laster();
}
接口的實現類
類1
/**
* 功能程序1
*
* @author Terry
* @date 2014-5-30
*
*/
public class MyAdvice1 implements Advice {
@Override
public void before() {
// TODO Auto-generated method stub
System.out.println("MyAdvice1開始");
}
@Override
public void laster() {
// TODO Auto-generated method stub
System.out.println("MyAdvice1結束");
}
}
類2
/**
* 功能程序2
*
* @author Terry
* @date 2014-5-30
*
*/
public class MyAdvice2 implements Advice {
@Override
public void before() {
// TODO Auto-generated method stub
System.out.println("MyAdvice2開始");
}
@Override
public void laster() {
// TODO Auto-generated method stub
System.out.println("MyAdvice2結束");
}
}
通用性動態代理框架
/**
* 自己寫的一個小型的通用性動態代理框架
* @param target:被代理的目標類
* @param advice:忠告,建議的意思。這裏是代理目標方法調所附加的代碼。(安全、事務、日誌等)
* @return 返回目標方法返回的結果
*/
private static Object getProxy(final Object target,final Advice advice) {
Object proxy = (Object)Proxy.newProxyInstance(
System.class.getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
advice.before();
System.out.println("方法:"+method.getName()+":參數"+args[0]);
Object object1 = method.invoke(target, args);
advice.laster();
return object1;
}
});
return proxy;
}
在main方法中運行如下代碼:
Collection proxy1 = (Collection)getProxy(new ArrayList(),new MyAdvice1());
Serializable proxy3 = (Serializable)getProxy(new ArrayList(),new MyAdvice1());//proxy1,proxy3為什麼對的?Serializable和Collection是ArrayList的接口
//ArrayList proxy4 = (ArrayList)getProxy(new ArrayList(),new MyAdvice1());//錯誤為什麼?
//System.out.println(proxy1.getClass());輸出打印:class $Proxy0,由此可以知道getProxy方法返回的是一個JVM動態生成的代理類,而此類繼承了Serializable和Collection接口,在上麵的框架方法中target.getClass().getInterfaces(), 有所體現,但是此類沒有繼承ArrayList,因為JVM動態生成的代理類必須繼承接口。
proxy3.equals("22");
System.out.println(proxy1.getClass());
proxy1.add("zxx");
proxy1.equals("333");
Runnable proxy2 = (Runnable) getProxy(new Thread(),new MyAdvice2());
proxy2.equals("aaa");
運行輸出:
MyAdvice1開始
方法:equals:參數22
MyAdvice1結束
class $Proxy0
MyAdvice1開始
方法:add:參數zxx
MyAdvice1結束
MyAdvice1開始
方法:equals:參數333
MyAdvice1結束
MyAdvice2開始
方法:equals:參數aaa
MyAdvice2結束
模擬Sptring框架(Spring的兩大核心Bean工廠和AOP框架)
類1、
package shipin56; import java.io.IOException; import java.io.InputStream; import java.util.Properties; import shipin49.Advice; /** * Java,BeanFactory工廠類:用來創建動態代理 * 這裏與之相關的應該是簡單工廠設計模式吧 * @author Terry * @date 2014-5-30 * */ public class BeanFactory { Properties props = new Properties(); /** * 將xml文件以流的方式傳進來 * @param ips,鍵值對的流 */ public BeanFactory(InputStream ips){ try { props.load(ips); } catch (IOException e) { // TODO Auto-generated catch block //做商業性項目要做異常處理 e.printStackTrace(); } } /** * 獲得Bean對象,如果找到的類是ProxyFactoryBean類型的,這返回此代理工廠生產的代理類JavaBean * @param name,在xml中鍵值對的key值 * @return JavaBean */ public Object getBean(String name){ //獲取xml中,key鍵為name的值 String className = props.getProperty(name); Object bean = null; try { Class clazz = Class.forName(className); bean = clazz.newInstance(); //如果獲得的bean對象是ProxyFactoryBean代理工廠類型的 if (bean instanceof ProxyFactoryBean){ ProxyFactoryBean pfb = (ProxyFactoryBean) bean; Advice advice = (Advice) Class.forName(props.getProperty(name+".advice")).newInstance(); Object target = Class.forName(props.getProperty(name+".target")).newInstance(); //設置切麵對象(安全、事務、日誌) pfb.setAdvice(advice); //設置被代理的目標 pfb.setTarget(target); Object proxy = pfb.getProxy(); //返回代理對象 return proxy; } } catch (Exception e) { } //返回其他對象 return bean; } }
類2
package shipin56; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import shipin49.Advice; /** * 在Spring中,這裏用的是接口,為了靈活吧 * 代理工廠類 * @author Terry * @date 2014-5-30 * */ public class ProxyFactoryBean { /** * 獲得代理對象 * @return 代理對象 */ public Object getProxy() { Object proxy = (Object)Proxy.newProxyInstance( //用被代理類的類加載器加載動態對象 target.getClass().getClassLoader(), //為代理類指定接口 target.getClass().getInterfaces(), new InvocationHandler() { @Override /** * */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub advice.before(); System.out.println("我在InvocationHandler類中的invoke()方法中:"+proxy.getClass().getName()); System.out.println("代理:"+proxy.getClass().getName()+"方法:"+method.getName()+":參數"+args[0]); Object object1 = method.invoke(target, args); advice.laster(); return object1; } }); return proxy; } //目標類 private Object target; //切麵對象 private Advice advice; public Object getTarget() { return target; } public void setTarget(Object target) { this.target = target; } public Advice getAdvice() { return advice; } public void setAdvice(Advice advice) { this.advice = advice; } }
類3:main方法
package shipin56; import java.io.IOException; import java.io.InputStream; import java.util.Collection; public class Main { /** * @param args * @throws IOException */ public static void main(String[] args) throws IOException { // TODO Auto-generated method stub InputStream ips = Main.class.getResourceAsStream("config.properties"); Object bean = new BeanFactory(ips).getBean("xxx"); Collection col = (Collection)bean; col.add("aaa"); } }
接口類
package shipin49; /** * 功能程序的接口 * * @author Terry * @date 2014-5-30 * */ public interface Advice { public void before(); public void laster(); }
實現類1
package shipin49; /** * 功能程序1 * * @author Terry * @date 2014-5-30 * */ public class MyAdvice1 implements Advice { @Override public void before() { // TODO Auto-generated method stub System.out.println("MyAdvice1開始"); } @Override public void laster() { // TODO Auto-generated method stub System.out.println("MyAdvice1結束"); } }
實現類2
package shipin49; /** * 功能程序2 * * @author Terry * @date 2014-5-30 * */ public class MyAdvice2 implements Advice { @Override public void before() { // TODO Auto-generated method stub System.out.println("MyAdvice2開始"); } @Override public void laster() { // TODO Auto-generated method stub System.out.println("MyAdvice2結束"); } }
XML配置,XML文件取名config.properties
#xxx=java.util.ArrayList
xxx=shipin56.ProxyFactoryBean
xxx.advice=shipin49.MyAdvice1
xxx.target=java.util.ArrayList
運行程序輸出結果:
MyAdvice1開始
我在InvocationHandler類中的invoke()方法中:$Proxy0
代理:$Proxy0方法:add:參數aaa
MyAdvice1結束
將XML裏麵的配置中的xxx.advice的值改為xxx.advice=shipin49.MyAdvice2運行輸出:
MyAdvice2開始
我在InvocationHandler類中的invoke()方法中:$Proxy0
代理:$Proxy0方法:add:參數aaa
MyAdvice2結束
知識點:
Proxy類
static Class<?> |
getProxyClass(ClassLoader loader,Class<?>... interfaces) 返回代理類的 java.lang.Class 對象,並向其提供類加載器和接口數組。 |
static Object |
newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 返回一個指定接口的代理類實例,該接口可以將方法調用指派到指定的調用處理程序。 |
Invocation(調用)Handler(處理程序)類
Object |
invoke(Object proxy,Method method,Object[] args) 在代理實例上處理方法調用並返回結果。 |
Properties類
void |
load(InputStream inStream) 從輸入流中讀取屬性列表(鍵和元素對)。 |
Class類
URL |
getResource(String name) 查找帶有給定名稱的資源。 |
23中常用的設計模式之-----------------------紅酒經銷:代理模式
最後更新:2017-04-03 08:26:18