靜態代理設計模式
代理設計模式最本質的特質:一個真實業務主題只完成核心操作,而所有與之輔助的功能都由代理類來完成。
?
例如,在進行數據庫更新的過程之中,事務處理必須起作用,所以此時就可以編寫代理設計模式來完成。
?
范例:結合傳統的代理設計模式以及以購物車CartDao為例來編寫代理設計模式
package so.strong.mall.proxy; import java.util.List; public interface CartDao {boolean insert(Cart cart) throws Exception;List<Cart> findAll() throws Exception; }
以上CartDao接口定義的方法,更行插入一定需要事務控制,對于查詢操作,不需要事務控制。
?
定義CartDao真實實現
package so.strong.mall.proxy; import java.util.List; public class CartDAOImpl implements CartDao{@Overridepublic boolean insert(Cart cart) throws Exception {System.out.println("=====執行數據增加操作=====");return false;}@Overridepublic List<Cart> findAll() throws Exception {System.out.println("=====執行數據列表操作=====");return null;} }
?
定義代理主題類
package so.strong.mall.proxy; import java.util.List; public class CartDAOProxy implements CartDao {private CartDao cartDao;public CartDAOProxy() {}public void setCartDao(CartDao cartDao) {this.cartDao = cartDao;}public void prepare() {System.out.println("=====取消掉jdbc的自動提交");}public void commit() {System.out.println("=====手工提交事務");}public void rollback() {System.out.println("=====出現錯誤,事務回滾");}@Overridepublic boolean insert(Cart cart) throws Exception {try {this.prepare();boolean flag = this.cartDao.insert(cart);this.commit();return flag;} catch (Exception e) {this.rollback();throw e;}}@Overridepublic List<Cart> findAll() throws Exception {return this.cartDao.findAll();} }
?
業務層現在并不關心到底是代理類還是真實主題類,它只關心一點,只要取得了CartDao接口對象就可以,那么這一操作可以通過工廠類來隱藏。
package so.strong.mall.proxy; public class DAOFactory {public static CartDao getCartDaoInstance() {CartDAOProxy proxy = new CartDAOProxy();proxy.setCartDao(new CartDAOImpl());return proxy;} }
此時業務層暫時不需要繼續進行,只需要通過客戶端模擬業務層調用即可。
public class TestDemo { public static void main(String[] args) throws Exception{CartDao dao = DAOFactory.getCartDaoInstance();dao.insert(new Cart());} } //=====取消掉jdbc的自動提交 //=====執行數據增加操作===== //=====手工提交事務
因為事務和處理本身與核心業務有關的功能,但是它不是核心,那么用代理解決是最合適的方式。
?
動態代理設計模式
上面給出的代理設計模式的確可以完成代理要求,但是有一個問題:如果說現在項目里面有200張數據表,那么至少也需要200個左右的DAO接口,如果用上面的代理設計模式,那么意味著除了編寫200個的DAO接口實現,還要編寫200個代理類,并且有意思的是,這些代理類實現幾乎相同。
以上的代理設計模式屬于靜態代理設計模式,只能夠作為代理模式的雛形出現,并不能購作為代碼使用的設計模式,為此專門引入了動態代理設計模式的概念。
即:利用一個代理類可以實現所有被代理的操作。
?
如果要想實現動態設計模式,那么必須首先觀察一個接口:java.lang.reflect.InvocatonHandler. ? 它里面有一個方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
這個方法就屬于代理中調用真實主題類的操作方法,這個方法里面的參數意義如下:
- Object proxy:表示代理類的對象;
- Method method:表示現在正在調用的方法;
- Object[] args:表示方法里面的參數。
但是這個方法沒有所對應的真實對象,所以需要在創建這個類對象的時候設置好真實代理對象。
?
如果要想找到代理對象則要使用java.lang.reflect.Proxy類來動態創建,此類主要使用以下方法:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException
此方法參數定義如下:
- ClassLoader loader :指的是取得對象的加載器;
- Class<?>[] interfaces: 代理設計模式的核心是圍繞接口進行的,所以此處必須取出全部的接口;
- InvocationHandler h:代理的實現類。
?
范例:使用動態代理實現上面的代理
CartDao不變,修改CartDAOProxy代理類
package so.strong.mall.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class CartDAOProxy implements InvocationHandler {private Object obj; //這個是真實對象主題/*** 將要操作的真實主題對象綁定到代理之中,而后返回一個代理類對象* @param obj 真實對象主題* @return 代理類對象*/public Object bind(Object obj) {this.obj = obj;return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(), this);}public void prepare() {System.out.println("=====取消掉jdbc的自動提交");}public void commit() {System.out.println("=====手工提交事務");}public void rollback() {System.out.println("=====出現錯誤,事務回滾");}//只要執行了操作方法,那么就一定會觸發invoke @Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object object = null;//接收返回值if (method.getName().contains("insert")) { //更新插入類操作this.prepare();try {object = method.invoke(this.obj, args); //反射調用方法this.commit();} catch (Exception e) {this.rollback();}} else {object = method.invoke(this.obj, args);//查詢操作不需要事務支持 }return object;} }
//修改工廠 package so.strong.mall.proxy; public class DAOFactory {public static Object getCartDaoInstance(Object realObject) {return new CartDAOProxy().bind(realObject);} }
//修改調用 package so.strong.mall.proxy; public class TestDemo {public static void main(String[] args) throws Exception{CartDao dao =(CartDao) DAOFactory.getCartDaoInstance(new CartDAOImpl());dao.insert(new Cart());} }
?
CGLIB實現動態代理設計模式
動態代理模式的確好用,而且也解決了代理類重復的問題,但是不管是傳統靜態代理或動態代理都有個設計缺陷,以動態代理為例:
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this); //傳入真實主題類,返回代理主題類
代理設計模式有一個硬性要求,就是類必須要有接口,所以業界很多人認為應該在沒有接口的環境下也能使用代理設計模式。
所以在此時在開源社區里面提供了一個組件包——CGLIB,利用此包可以在沒有接口的情況下也能夠使用動態代理設計模式,它是模擬的類。
如果要想使用CGLIB,那么必須首先搞清楚對應關系:
- Proxy:net.sf.cglib.proxy.Enhancer
- InvocationHandler:net.sf.cglib.proxy.MethodInterceptor
- 真實主題調用:net.sf.cglib.proxy.MethodProxy
老師課上使用的是引入CGLIB的jar包,我去mvn倉庫找了一下,找到了一個cglib,放到pom里面發現也可以。
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>2.2.2</version> </dependency>
?
范例:使用CGLIB實現動態代理設計模式
package so.strong.mall.proxy; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method;class ItemDAOImpl {public void insert(Item item) {System.out.println("=====增加操作=====");} }class MyProxy implements MethodInterceptor {private Object target; //真實操作主題public MyProxy(Object target) {this.target = target;}@Overridepublic Object intercept(Object proxy, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {Object object = null;this.prepare();object = method.invoke(this.target, args);this.commit();return object;}public void prepare() {System.out.println("=====取消掉jdbc的自動提交=====");}public void commit() {System.out.println("=====手工提交事務=====");} }public class TestCGLIB {public static void main(String[] args) {ItemDAOImpl itemDAO = new ItemDAOImpl(); //真實主題對象//代理設計模式之中必須要有公共的集合點,例如:接口,而CGLIB沒有接口Enhancer enhancer = new Enhancer(); //創建一個代理工具類enhancer.setSuperclass(ItemDAOImpl.class); //設置一個虛擬的父類enhancer.setCallback(new MyProxy(itemDAO)); //設置代理的回調操作ItemDAOImpl proxyDao = (ItemDAOImpl) enhancer.create();proxyDao.insert(new Item());} }
可以發現此時沒有了對接口的依賴,也可以實現動態代理設計,但是需要模擬代理的父類對象。