1. 程式人生 > 程式設計 >Java動態代理靜態代理例項分析

Java動態代理靜態代理例項分析

代理模式:為其他物件提供一種代理以控制某個物件的訪問。用在:在某些情況下,一個客戶不想或者不能直接訪問另一個物件,而代理物件可以在客戶端和目標物件之前起到中介的作用,代理物件還可以完成它附加的操作。

例子:就像房東、租客、中介的關係。中介(代理物件)為房東(真實物件)出租房子,租客(客戶)通過中介(代理物件)來找房子租房子,中介完成了租房以後可以收取中介費(附加操作)。

先看看靜態代理模式,通過上面對代理模式的理解,可以瞭解到代理模式:即不直接通過new一個真實物件來呼叫方法,而是通過代理物件來呼叫一個方法,所以代理物件包含真實物件的引用。下面看一下程式碼

介面:Subject包含一個方法

 package com.example.designpattern.proxy; 
 public interface Subject {
 void request();
 }

RealSubject類,實現了Subject介面,為了簡單起見,方法簡單的輸出一句話:

package com.example.designpattern.proxy;
public class RealSubject implements Subject {
//真是角色實現了
public void request() {
System.out.println("From real subject");
}
}

代理類ProxySubject,也要實現Subject介面,實現Subject裡面的方法,但是在這裡裡面是通過呼叫真實物件來實現的。

package com.example.designpattern.proxy;

public class ProxySubject implements Subject {

private RealSubject realSubject; //代理角色內部引用了真實角色

//代理角色實現目標動作
public void request() {

this.preRequest(); //在真實角色操作之前所附加的操作
if (realSubject == null){
realSubject = new RealSubject();
}
realSubject.request(); // 真實角色所完成的事情
this.afterRequet(); //在真實角色操作之後附加的操作
}
//代理角色之前完成的動作
private void preRequest(){
System.out.println("pre request");
}
//代理角色之後完成的動作
private void afterRequet(){
System.out.println("after request");
}
}

客戶呼叫者

package com.example.designpattern.proxy;

public class Client {
public static void main(String[] args) {
ProxySubject proxy = new ProxySubject();
//通過代理物件來呼叫方法
proxy.request(); 
}
}

靜態代理:

可以執行一下這些程式碼哦, 可以在Client類中看到,是通過代理ProxySubject的物件proxy來呼叫方法的,在代理類ProxySubject中,有一個真實物件的引用,在代理物件的中request()方法呼叫了真實物件的方法。這樣的模式叫做代理模式。

優點是:

1. 代理模式能將代理物件與真實物件被呼叫的目標物件分離。

2. 一定程度上降低了系統的耦合度,擴充套件性好。

代理類中包含了對真實主題的引用,這樣做也有缺點:

1. 真實物件與代理類一一對應,增加真實類也要增加代理類,這樣做會快速的增加類的數量,使得系統變得複雜。

2. 設計代理以前真實主題必須事先存在,不太靈活。

採用動態代理可以解決以上問題,動態代理是相對於靜態代理來說的。

可能你也會說怎麼樣實現動態建立例項,以前我們建立例項不都是通過new 的方式來實現的嗎?

Hello hi = new Hello();

那麼動態建立例項是由Java提供的功能,就不需要我們去new 物件,他已經定義好了靜態方法Proxy.newProxyInstance(),只要傳入引數呼叫就可以。Java文件裡面有哦,如圖:

Java動態代理靜態代理例項分析

Java標準庫提供了一種動態代理(DynamicProxy)的機制:可以在執行期動態建立某個interface的例項。

引數解釋:

 Proxy.newProxyInstance(
 ClassLoader loader,// 傳入ClassLoader
 Class<?>[] interfaces,// 傳入要呼叫的介面的方法陣列
 InvocationHandler h); //傳入InvocationHandler 的例項

下面看一下動態代理例子程式碼:

Subject 介面

package design.dynamicproxy;
public interface Subject {
void request(String str);
}

RealSubject類 實現 Subject 介面

package design.dynamicproxy;
public class RealSubject implements Subject {
@Override
public void request(String str) {
System.out.println("From Real Subject!" + " args:" + str );
}
}

動態代理類DynamicSubject 實現了InvocationHandler,重寫invoke()方法

package design.dynamicproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
* 該代理類的內部屬性時Object型別,實際使用時,使用該類的構造方法傳遞一個物件
* 此外該類還實現了invoke() 方法,該方法中的method.invoke() 其實就是要呼叫被代理物件的要執行的方法
* 方法引數是object,表示該方法從屬於object物件,通過動態代理類,我們可以在執行真是物件的
* 方法前後可以加入一些額外的方法
*/
public class DynamicSubject implements InvocationHandler {

//引入的型別是Object的,可以隨便傳入任何一個物件
private Object object;

public DynamicSubject(Object object){
this.object = object;
}
@Override
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable {
System.out.println("before calling:" + method);
//等價於realSubject的request() 方法,如果這裡不呼叫的話,不會呼叫Method 物件中的方法
method.invoke(object,args);
System.out.println("after calling:" + method);
return null;
}
}

Client類

package design.dynamicproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Client {
	public static void main(String[] args) {
		RealSubject realSubject = new RealSubject();
		InvocationHandler handler = new DynamicSubject(realSubject);
		Class<?> classType = handler.getClass();
		//下面的程式碼一次性生成代理
		// 動態生成了class com.sun.proxy.$Proxy0 的例項,
		Subject subject = (Subject) Proxy.newProxyInstance(classType.getClassLoader(),realSubject.getClass().getInterfaces(),handler);
		subject.request("eather");
		System.out.println(subject.getClass());
		//輸出class com.sun.proxy.$Proxy0,可以看到Proxy.newProxyInstance() 是系統自動生成的例項
	}
}

在Client中可以看到,我們這裡呼叫方法的是 subject.request("eather"); 這個物件subject 不是通過new DynamicSubject()生成的,而是Java內部寫好的方法在執行時動態生成物件;可能有人說

InvocationHandler handler = new DynamicSubject(realSubject);

這裡不是通過new new DynamicSubject(realSubject); 生成了一個物件嗎? 是的,但是它是InvocationHandler 型別的,主要是傳遞一個InvocationHandler型別引數給Proxy.newProxyInstance(); 即最後一個引數。通過Client類的最後一句輸出可以看到它是 class com.sun.proxy.$Proxy0 ,這是Java執行時生成的。

解決了靜態代理的難題:1. 真實物件與代理類一一對應,增加真實類也要增加代理類,這樣做會快速的增加類的數量,使得系統變得複雜。 為什麼這麼說呢, 因為代理類引用的型別是Object的,可以隨便傳入任何一個物件,當真實類增加時,代理類不用增加,new DynamicSubject(object); new的時候把要傳入的物件傳進去即可。

下面是Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h); 這個方法的原始碼啦,可以看看,深入瞭解一下

public static Object newProxyInstance(ClassLoader loader,InvocationHandler h)
throws IllegalArgumentException
{
	Objects.requireNonNull(h);
	final Class<?>[] intfs = interfaces.clone();
	final SecurityManager sm = System.getSecurityManager();
	if (sm != null) {
		checkProxyAccess(Reflection.getCallerClass(),loader,intfs);
	}
	/*
* Look up or generate the designated proxy class.
生成一個代理類物件
*/
	Class<?> cl = getProxyClass0(loader,intfs);
	/*
* Invoke its constructor with the designated invocation handler.
使用指定的呼叫處理程式呼叫其建構函式。就是使用InvocationHandler 例項呼叫【要呼叫方法的那個類】的構造方法
*/
	try {
		if (sm != null) {
			checkNewProxyPermission(Reflection.getCallerClass(),cl);
		}
		final Constructor<?> cons = cl.getConstructor(constructorParams);
		final InvocationHandler ih = h;
		if (!Modifier.isPublic(cl.getModifiers())) {
			AccessController.doPrivileged(new PrivilegedAction<Void>() {
				public Void run() {
					cons.setAccessible(true);
					return null;
				}
			}
			);
		}
		return cons.newInstance(new Object[]{
			h
		}
		);
	}
	catch (IllegalAccessException|InstantiationException e) {
		throw new InternalError(e.toString(),e);
	}
	catch (InvocationTargetException e) {
		Throwable t = e.getCause();
		if (t instanceof RuntimeException) {
			throw (RuntimeException) t;
		} else {
			throw new InternalError(t.toString(),t);
		}
	}
	catch (NoSuchMethodException e) {
		throw new InternalError(e.toString(),e);
	}
}

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。