1. 程式人生 > >Java設計模式詳談(二):代理

Java設計模式詳談(二):代理

經過上一篇對於單例模式的心得分享,今天來聊一聊第二種設計模式 ,關於代理模式的個人理解。代理你可以把它想象成中間人、中介等等,不需要你親自去處理你想做的事,把你想要的告訴他們,讓他們去幫你實現,完成後再把最終的結果通知你。

總的來說代理模式分為兩種:一種是靜態代理,一種是動態代理。


那麼首先來說一下靜態代理:


靜態代理

靜態代理在使用的時候,需要定義介面(Interface) 或者是父類(Parent  Class),被代理的物件與代理物件一起實現相同的介面或者是繼承相同的父類。

下面舉個案例:


介面

/**
 * 代理介面
 * @author lyr
 * @date 2017年11月27日
 */
public interface IUserDao {
	void save();
}



目標物件(需求方,俗稱客戶方)

/**
 * 目標物件實現介面
 * @author lyr
 * @date 2017年11月27日
 */
public class UserDao implements IUserDao {

	public void save() {
		System.out.println("資料正在儲存。。。。。");
		System.out.println("資料儲存成功!");
	}

}



代理物件(中間人,代理者)

/**
 * 代理物件,靜態代理
 * @author lyr
 * @date 2017年11月27日
 */
public class UserDaoProxy implements IUserDao{

	//接收儲存目標物件
	private IUserDao iUserDao;

	public UserDaoProxy(IUserDao iUserDao) {
		this.iUserDao = iUserDao;
	}

	public void save() {
		System.out.println("開啟事務...");
		iUserDao.save(); //執行目標物件方法
		System.out.println("提交事務...");
	}

}



測試

/**
 * 測試類
 * @author lyr
 * @date 2017年11月27日
 */
public class TestProxy {
	public static void main(String[] args) {
		//目標物件(客戶)
		UserDao userDao = new UserDao();
		//代理物件(中間人),把目標物件傳遞給代理物件,建立代理關係
		UserDaoProxy proxy = new UserDaoProxy(userDao);
		//執行代理方法
		proxy.save();
	}
}



靜態代理的優缺點:


優點:能夠在不修改目標物件的功能的前提下,對目標物件進行擴充套件;

缺點:目標物件要與代理物件實現一樣的介面,會產生過多的代理類,並且一旦介面進行修改變動或者增加方法,那麼目標物件與代理物件都需要進行維護。



動態代理


針對靜態代理中出現的問題,可以通過動態代理來解決掉。那麼動態代理有哪些特別的地方呢?其實在動態代理方面,有兩種選擇(JDK動態代理和CGLib動態代理)。


1.1JDK動態代理

JDK動態代理的幾個特點:

(1)代理物件不需要實現介面;

(2)代理物件的生成,是利用JDK的API,動態的在記憶體中構建代理物件。(需要我們指定建立代理物件/目標物件實現的介面的型別);

(3)JDK代理也叫介面代理。


JDK生成代理物件的API

代理類所在包 :java.lang.reflect.Proxy

先看下程式碼實現的例子:

/**
 * 建立動態代理物件
 * 動態代理不需要實現介面。但是需要指定介面型別
 * @author lyr
 * @date 2017年11月27日
 */
public class ProxyFactory {

    //維護一個目標物件
    private Object target;
    public ProxyFactory(Object target){
        this.target=target;
    }

   //給目標物件生成代理物件
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
			public Object invoke(Object proxy, Method method, Object[] args) throws Exception{
			    System.out.println("開始事務...");
	                    //執行目標物件方法
	                    Object returnValue = method.invoke(target, args);
	                    System.out.println("提交事務...");
	                    return returnValue;
			}
                }
        );
    }
}

測試程式碼

//目標物件
IUserDao iUserDao = new UserDao();
//為目標物件建立代理物件
IUserDao target = (IUserDao) new ProxyFactory(iUserDao).getProxyInstance();
//執行代理方法
target.save();

通過上面的例子可以看到,JDK動態代理在代理物件不需要實現介面,但是目標物件一定要實現介面,否則就不能實現動態代理。這搞啥子嘛,問題還是有,只不過少了。當然,還有另外一種動態代理方式:CGLib動態代理。


1.2 CGLib動態代理

CGLib動態代理,也叫做子類代理,它是在記憶體中構建一個子類物件從而實現對目標物件功能的擴充套件。

   (1)JDK動態代理有一個限制,就是動態代理的物件必須要實現一個或多個介面,否則就需要考慮CGLib動態代理來實現;

   (2)CGLib是一個強大的高效能的程式碼生成包,可以在執行期擴充套件java類與實現java介面。這一點在Spring AOP中可以見到,可以對方法進行很好的攔截;

   (3)CGLib的底層是通過使用一個小而快的位元組碼處理框架ASM來轉換位元組碼並生成新的類,這裡不鼓勵直接去使用ASM,這點要求你必須要對JVM的內部結構包括class檔案的格式和指令集都很熟悉才可以。

其實這裡還有需要注意的是:(1)代理的類不能是final修飾的,否則會直接報錯;(2)目標物件中的方法如果有是final或者static修飾的,就不會被攔截。

在使用CGLib動態代理的時候,你需要引入它的jar包才能使用它,但是目前Spring的核心包裡面已經包括CGLib的相關功能(Spring的範圍有點廣),所以只需要引入spring-core-4.2.2.jar就行了。接下來,下面的相關程式碼:


先看目標物件,沒有實現任何介面

/**
 * 目標物件,沒有實現任何介面
 * @author lyr
 * @date 2017年11月27日
 */
public class UsersDao {

	public void save(){
		System.out.println("正在儲存資料...");
		System.out.println("資料儲存成功!");
	}
}

再來看下對應的CGLib子類代理工廠

/**
 * CGLib子類代理工廠,對UserDao在記憶體中動態的構建一個子類物件
 * @author lyr
 * @date 2017年11月27日
 */
public class ProxyFactorys implements MethodInterceptor{

    private Object target;
	
    public ProxyFactorys(Object target) {
	this.target = target;
    }

    public Object getProxyInstance(){      
      //這個作為工具類,它允許為非介面型別建立一個Java代理,動態的建立給定型別的子類並且攔截所有的方法
      Enhancer en = new Enhancer();
      en.setSuperclass(target.getClass());
      en.setCallback(this);
      return en.create();
    }

    public Object intercept(Object obj, Method method, Object[] args,MethodProxy proxy) throws Throwable {
      System.out.println("開始事務...");
      Object returnValue = method.invoke(target, args);
      System.out.println("提交事務...");return returnValue;
    }
}


最終的測試

/**
 * 測試類
 * @author lyr
 * @date 2017年11月27日
 */
public class TestProxy {
    public static void main(String[] args) {
        UsersDao userDao = new UsersDao();
        UsersDao target = (UsersDao) new ProxyFactorys(userDao).getProxyInstance();
	target.save();
    }
}



到這裡你可以發現,它和JDK代理是不是也有點相似之處,當然這兩種動態代理要根據具體的情況進行使用,一般來說,在Spring的AOP裡面,如果加入的容器的目標物件有實現介面,就用JDK動態代理,否則的話,就用CGLib動態代理。


代理模式就說這麼多吧,感謝各位, 下一篇再見!