1. 程式人生 > >代理機制的使用及個人見解

代理機制的使用及個人見解

一、何為代理?為何要用代理?

個人愚見,代理就是代替處理的意思。即,用另外的類(代理類)來處理已經存在的類(原類即被代理的類)。

使用代理這種模式,可以在不改變原類程式碼的基礎上實現對原類方法的攔截,方法引數的識別等操作,也就是在呼叫原類方法的時候可以加以判別、限制等操作以達到自己的需求。使用這種方法的好處就是不需要改動原類程式碼(要知道,隨意改動程式碼是一件很危險的事,很容易造成已有邏輯的混亂)也可以在呼叫原類時新增新的需求。

二、代理的分類

代理大致可以分為兩類:靜態代理和動態代理,動態代理又有兩種方式(JDK和CGLib)。

下面會講解各代理的實現以及其中程式碼的大致理解。

三、代理的實現

3.1、靜態代理

靜態代理的實現特別簡單,那就讓代理類也實現該介面,在代理類的方法中加以攔截,實現過程如下(由於靜態代理是臨時寫的,原類就用為JDK準備的原類吧,只是名字的區別,不必在意)。

3.1.1、首先定義一個將要實現的介面

package com.about_jdk.classes;

public interface Iinterface1 {
	void setId(String id);
	String getId();
}

 

package com.about_jdk.classes;

public interface Iinterface2 {
	void setPassword(String password);
	String getPassword();
}

3.1.2、再定義原類

package com.about_jdk.classes;

public class JDKClassModel implements Iinterface1, Iinterface2{
	private String id;
	private String password;
	
	public JDKClassModel() {
	}

	@Override
	public String getId() {
		System.out.println("ClassModel.getId()");
		
		return id;
	}

	@Override
	public void setId(String id) {
		this.id = id;
		System.out.println("ClassModel.setId(): id= " + id);
	}

	@Override
	public void setPassword(String password) {
		this.password = password;
		System.out.println("ClassModel.setPassword(): password= " + password);
	}

	@Override
	public String getPassword() {
		System.out.println("ClassModel.getPassword()");
		
		return password;
	}

3.1.3、然後再定義代理類

package com.staticProxy.proxy;

import com.about_jdk.classes.Iinterface1;
import com.about_jdk.classes.Iinterface2;
import com.about_jdk.classes.JDKClassModel;

public class staticProxy implements Iinterface1, Iinterface2{
	JDKClassModel model;
	
	public staticProxy(JDKClassModel model) {
		this.model = model;
	}

	@Override
	public void setId(String id) {
		System.out.println("置前攔截(staticProxy.setId)");
		model.setId(id);
		System.out.println("置後攔截(staticProxy.setId)");
	}

	@Override
	public String getId() {
		System.out.println("置前攔截(staticProxy.getId)");
		String id = model.getId();
		System.out.println("置後攔截(staticProxy.getId)");
		
		return id;
	}

	@Override
	public void setPassword(String password) {
		System.out.println("置前攔截(staticProxy.setPassword)");
		model.setPassword(password);
		System.out.println("置後攔截(staticProxy.setPassword)");
	}

	@Override
	public String getPassword() {
		System.out.println("置前攔截(staticProxy.getPassword)");
		String password = model.getPassword();
		System.out.println("置後攔截(staticProxy.getPassword)");
		return password;
	}

3.1.4、定義測試類

package com.about_jdk.demo;

import com.about_jdk.classes.JDKClassModel;
import com.staticProxy.proxy.staticProxy;

public class StaticProxyTest {

	public static void main(String[] args) {
		staticProxy sp = new staticProxy(new JDKClassModel());
		sp.setId("123");
		sp.setPassword("000");
		System.out.println("mian():id= " + sp.getId() + " password= " + sp.getPassword());
	}

}

執行結果:

置前攔截(staticProxy.setId)
ClassModel.setId(): id= 123
置後攔截(staticProxy.setId)
置前攔截(staticProxy.setPassword)
ClassModel.setPassword(): password= 000
置後攔截(staticProxy.setPassword)
置前攔截(staticProxy.getId)
ClassModel.getId()
置後攔截(staticProxy.getId)
置前攔截(staticProxy.getPassword)
ClassModel.getPassword()
置後攔截(staticProxy.getPassword)
mian():id= 123 password= 000

小結:這個方法很簡單,相信大家都能看的懂,當然,我所使用的類是有介面的,如果不使用介面,用這種方式代理,就必須在代理得中通過傳進來的物件點出原類的方法並再手動定義一次原類中的方法,這當然也是可以實現的,但應該沒人願意這麼做吧。這裡就說一下它的侷限性:假想一下,我們如果需要加入一個新的代理介面,就必須在代理類中實現新的介面,這是很麻煩,使用起來很不方便的。當然,也因此,將它稱之為靜態代理。接下來,我們看看比較高大上的動態代理。

3.2、JDK動態代理

JDK的代理是有介面的代理,攔截的也是介面中的方法,接下來我們看看他的實現。

3.2.1 定義介面(和上面靜態代理的介面一致)

package com.about_jdk.classes;

public interface Iinterface1 {
	void setId(String id);
	String getId();
}
package com.about_jdk.classes;

public interface Iinterface2 {
	void setPassword(String password);
	String getPassword();
}

3.2.2 定義原類(與上面靜態代理的一樣)

package com.about_jdk.classes;

public class JDKClassModel implements Iinterface1, Iinterface2{
	private String id;
	private String password;
	
	public JDKClassModel() {
	}

	@Override
	public String getId() {
		System.out.println("ClassModel.getId()");
		
		return id;
	}

	@Override
	public void setId(String id) {
		this.id = id;
		System.out.println("ClassModel.setId(): id= " + id);
	}

	@Override
	public void setPassword(String password) {
		this.password = password;
		System.out.println("ClassModel.setPassword(): password= " + password);
	}

	@Override
	public String getPassword() {
		System.out.println("ClassModel.getPassword()");
		
		return password;
	}
	
}

3.2.3 定義代理類

package com.about_jdk.proxy;

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

public class JdkProxy {
	
	public JdkProxy() {
	}
	
	@SuppressWarnings("unchecked")
	public <T> T getProxy(Class<?> klass) throws Exception {    // 方法過載,為了方面使用者呼叫
		Object obj = klass.newInstance();
		
		return (T) getProxy(klass, obj);
	}
	
	@SuppressWarnings("unchecked")
	public <T> T getProxy(Object obj) {                          // 方法過載,為了方便使用者呼叫
		Class<?> klass = obj.getClass();
		return (T) getProxy(klass, obj);
	}
	
	@SuppressWarnings("unchecked")
	private <T> T getProxy(Class<T> klass, Object obj) {       // 返回值為泛型,可讓使用者不再進行型別強轉
		return (T) Proxy.newProxyInstance(
				klass.getClassLoader(),      // 得到原類的載入
				klass.getInterfaces(),       // 得到原類實現的介面
				new InvocationHandler() {    // 檢視原碼,可以知道,這其實就是一個要實現如下invoke方法的介面
					
					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
						// 這裡的三個引數分別是:代理類的物件(proxy),原類的方法(method),原類方法中的引數陣列(args)
						Object result = null;
						
						System.out.println("前置攔截...");  
						// 這裡的攔截可以加一些判斷和自己需要的操作,讓攔截器發揮真正的作用,這也是代理的必要所在,這裡只講代理的實現,便不再細講
						result = method.invoke(obj, args);  
						// 這是一個方法的反射,其中需要的東西有原類中的方法(method),原類方法中的引數陣列(args),以及原類的物件(obj)
						// 由執行結果可知,這裡反射的就是原類中的方法,而這正式代理的關鍵一步,通過這一步便將原類和代理類聯絡在了一起。
						// 我們可以看到,invoke傳進來的三個引數用到了其中兩個,那剩下的一個代理物件究竟有何作用。有程式碼可知,我們並沒有得到原類的方法名及引數,
						// 因此,我們沒辦法對其進行反射,所以我們可以猜想,java內部幫我們實現了這一步,而其所需要的就是我們的代理物件。
						System.out.println("後置攔截...");
						// 置後攔截同樣可以新增很多自己需要的操作。
						
						return result;
					}
				});
		
	}
}

3.2.4、測試

package com.about_jdk.demo;

import com.about_jdk.classes.JDKClassModel;
import com.about_jdk.classes.Iinterface1;
import com.about_jdk.classes.Iinterface2;
import com.about_jdk.proxy.JdkProxy;

public class JDKTest {

	public static void main(String[] args) {
		Iinterface1 ii1 = new JdkProxy().getProxy(new JDKClassModel());
		ii1.setId("06163063");
		System.out.println("ii1.getId(): id= " + ii1.getId());
		try {
			Iinterface2 ii2 = new JdkProxy().getProxy(JDKClassModel.class);
			ii2.setPassword("000000");
			System.out.println("ii2.getPassword(): password= " + ii2.getPassword());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

執行結果:

前置攔截...
ClassModel.setId(): id= 06163063
後置攔截...
前置攔截...
ClassModel.getId()
後置攔截...
ii1.getId(): id= 06163063
前置攔截...
ClassModel.setPassword(): password= 000000
後置攔截...
前置攔截...
ClassModel.getPassword()
後置攔截...
ii2.getPassword(): password= 000000

小結:這個代理方式,代理的是介面中的方法,上述返回型別為泛型<T>,也是為了使用者呼叫時不必再次強轉。對於非介面實現的類中的方法,我們只能用CGLib代理來實現。

3.3、CGLib代理

3.3.1、定義原類

package com.about_cgl.classes;

public class CGLClassModel {
	private String id;
	private int money = 1000;
	
	public CGLClassModel() {
	}
	
	public void setId(String id) {
		this.id = id;
		System.out.println("CGLClassModel.setId(): id=" + id);
	}
	
	public String getId() {
		System.out.println("CGLClassModel.getId()");
		return id;
	}
	
	public int changeMoney(int newMoney) {
		this.money = newMoney;
		System.out.println("CGLClassModel.changeMoney()");
		
		return money;
	}
}

3.3.2、定義代理類

package com.about_cgl.proxy;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CGLProxy {

	public CGLProxy() {
	}
	
	@SuppressWarnings("unchecked")
	public <T> T getProxy(Class<?> klass) throws InstantiationException, IllegalAccessException {
		Object obj = klass.newInstance();
		return (T) getProxy(klass, obj);
	}
	
	@SuppressWarnings("unchecked")
	public <T> T getProxy(Object obj) {
		Class<?> klass = obj.getClass();
		
		return (T) getProxy(klass, obj);
	}
	
	@SuppressWarnings("unchecked")
	private <T> T getProxy(Class<?> klass, Object obj) {
		Enhancer enhancer = new Enhancer();                // 定義並例項一個增強類
		enhancer.setSuperclass(klass);                     // 指定父類
		enhancer.setCallback(new MethodInterceptor() {     // 回撥該增強類
			
			@Override
			public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
				Object result = null;
				
				if(method.getName().equals("changeMoney")) {
					System.out.println("有人修改money,已攔截!!!");
					return null;
				}else {
					System.out.println("CGL前置攔截...");
					result = method.invoke(obj, args);
				}
				
				System.out.println("CGL後置攔截...");
				
				return result;
			}
		});
		return (T) enhancer.create();  // 返回值為泛型,使使用者不必再次強轉
	}
}

3.3.3、測試

package com.about_jdk.demo;

import com.about_cgl.classes.CGLClassModel;
import com.about_cgl.proxy.CGLProxy;

public class CGLibTest {

	public static void main(String[] args) {
		try {
			 CGLClassModel cglClassModel = new CGLProxy().getProxy(CGLClassModel.class);
			 cglClassModel.setId("123#");
			 cglClassModel.changeMoney(900);
		} catch (InstantiationException | IllegalAccessException e) {
			e.printStackTrace();
		}
		
	}

}

執行結果:

CGL前置攔截...
CGLClassModel.setId(): id=123#
CGL後置攔截...
有人修改money,已攔截!!!

小結:這裡只是對代理的簡單實現,沒有新增任何的騷操作。

總結:靜態代理不再多說,對於動態代理,反射是它的基礎,由程式碼可以看出,我們在呼叫的時候用的都是代理類,在代理類的執行中,先是執行前置攔截(可以加一些判斷,如CGLib代理中給出的那樣),再就是執行反射方法,這一步將執行的是原類的.class檔案,對原類程式碼不會造成威脅,卻有確確實實執行的是原類的程式碼(這是反射機制的功勞,詳細瞭解可查詢反射機制的實現),最後就是執行後置攔截,當然我們也可以新增一個異常攔截,對對原類反射執行的過程加以監控。

代理是一個很牛逼的模式,它的存在,讓我們對已有的程式碼在不傷害其(改變其原碼)的同時加以更多的控制。

希望這篇博文會對您有所幫助。