springAOP指的是在spring中的AOP,什么是AOP,相對於java中的面向對象(oop),在面向對象中一些公共的行為,像日志記錄,權限驗證等如果都使用面向對象來做,會在每個業務方法中都寫上重復的代碼,造成代碼的冗余。而AOP指的是面向切面編程,定義一個切面,用切面去切相應的方法,就可以織入相關的邏輯。面向切面編程使用代理模式
一、代理模式
代理模式作為23種經典設計模式之一,其比較官方的定義為“為其他對象提供一種代理以控制對這個對象的訪問”,簡單點說就是,之前A類自己做一件事,在使用代理之后,A類不直接去做,而是由A類的代理類B來去做。代理類其實是在之前類的基礎上做了一層封裝。java中有靜態代理、JDK動態代理、CGLib動態代理的方式。靜態代理指的是代理類是在編譯期就存在的,相反動態代理則是在程序運行期動態生成的,
二、靜態代理
靜態代理,簡單點來說就是在程序運行之前,代理類和被代理類的關系已經確定。靜態代理的實現方式一般有以下幾個步驟,首先要定義一個公共的接口,供代理類和被代理類實現,如下
package cn.com.staticProxy; /** * * *<p>Title: IUserDao</p> * <p>Description:公共的接口</p> * <p>Company: </p> * @author Administrator * @date 2019年4月24日 下午4:15:12 */ public interface IUserDao { void save(); void find(); }
然后是代理類和被代理類,先看被代理類,
package cn.com.staticProxy; public class UserDao implements IUserDao{ /** * 被代理類或者叫代理目標類 */ @Override public void save() { // TODO Auto-generated method stub System.out.println("模擬保存用戶"); } @Override public void find() { // TODO Auto-generated method stub System.out.println("模擬查找用戶"); } }
從被代理類上可以看到,被代理類要做的就是save和find操作,如果不使用代理模式,那么我們的使用方式則是直接使用,如下
package cn.com.staticProxy; /** * * *<p>Title: Test2</p> * <p>Description:直接使用被代理類 </p> * <p>Company: </p> * @author Administrator * @date 2019年4月24日 下午4:18:39 */ public class Test2 { public static void main(String[] args) { // TODO Auto-generated method stub IUserDao udp=new UserDao(); udp.save(); System.out.println("-------------------"); udp.find(); } }
通過測試方法,可以得出執行方法的結果如下,
模擬保存用戶
-------------------
模擬查找用戶
下面看代理類,
package cn.com.staticProxy; public class UserDaoProxy implements IUserDao { private UserDao ud = new UserDao(); @Override public void save() { // TODO Auto-generated method stub System.out.println("代理操作,開啟事務"); ud.save(); System.out.println("代理操作,關閉事務"); } @Override public void find() { // TODO Auto-generated method stub System.out.println("代理操作,開啟事務"); ud.find(); System.out.println("代理操作,關閉事務"); } }
可以看到代理類的實現邏輯是在代理類中持有一個被代理類的實例,通過被代理類實例調用被代理對象的方法,另外在方法之前前后均可加入其它的方法處理邏輯,
最后,看使用代理類的測試方法,
package cn.com.staticProxy; public class Test { public static void main(String[] args) { // TODO Auto-generated method stub IUserDao udp=new UserDaoProxy(); udp.save(); System.out.println("-------------------"); udp.find(); } }
返回的執行結果如下,
代理操作,開啟事務
模擬保存用戶
代理操作,關閉事務
-------------------
代理操作,開啟事務
模擬查找用戶
代理操作,關閉事務
對比,使用靜態代理和不使用靜態代理,可以發現使用了代理之后,可以在被代理方法的執行前或后加入別的代碼,實現諸如權限及日志的操作。
但,靜態代理也存在一定的問題,如果被代理方法很多,就要為每個方法進行代理,增加了代碼維護的成本。有沒有其他的方式可以減少代碼的維護,那就是動態代理。
三、JDK動態代理
JDK提供了動態代理的方式,可以生成代理類,被代理類和接口沿用靜態代理中的IUserDao和UserDao,UserDao為被代理類,下面看代理類,
package cn.com.dynamicProxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import cn.com.staticProxy.IUserDao; import cn.com.staticProxy.UserDao; /** * * *<p>Title: DynamicProxy</p> * <p>Description: 動態代理類</p> * <p>Company: </p> * @author Administrator * @date 2019年4月24日 下午4:35:00 */ public class DynamicProxy implements InvocationHandler{ //被代理類的實例 private IUserDao iud=null; @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub Object result=null; System.out.println("開始JDK動態代理"); method.invoke(iud, args); System.out.println("結束JDK動態代理"); return result; } //構造方法 public DynamicProxy(IUserDao iud){ this.iud=iud; } }
需要實現JDK中的InvocationHandler接口,實現其中的invoke方法,在此方法中通過反射的方式調用被代理類的方法,可以在方法執行前或后進行別的處理,下面看測試代碼
package cn.com.dynamicProxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import cn.com.staticProxy.IUserDao; import cn.com.staticProxy.UserDao; public class Test2 { public static void main(String[] args) { // TODO Auto-generated method stub UserDao ud=new UserDao(); DynamicProxy dp=new DynamicProxy(ud); //生成代理對象 IUserDao iud=(IUserDao)Proxy.newProxyInstance(ud.getClass().getClassLoader(), ud.getClass().getInterfaces(), dp); iud.save(); System.out.println("--------------"); iud.find(); } }
在測試代碼中通過Proxy類的newProxyInstance方法,生成代理類的實例iud,需要三個參數,第一個為被代理類的類加載器,第二個為被代理類實現的接口,第三個則為invocationHandler的實現類,這樣就生成了代理對象,然后通過代理對象調用方法執行結果如下,
開始JDK動態代理
模擬保存用戶
結束JDK動態代理
--------------
開始JDK動態代理
模擬查找用戶
結束JDK動態代理
另外,由於第三個參數為一個接口,還可以使用匿名內部類的方式進行書寫,其實現代碼如下,
package cn.com.dynamicProxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import cn.com.staticProxy.IUserDao; import cn.com.staticProxy.UserDao; public class Test { public static void main(String[] args) { // TODO Auto-generated method stub //被代理類的實例 UserDao ud = new UserDao(); IUserDao iud = (IUserDao) Proxy.newProxyInstance(ud.getClass().getClassLoader(), ud.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub Object result = null; if ("find".equals(method.getName())) { result = method.invoke(ud, args); } else { System.out.println("開始JDK代理"); result = method.invoke(ud, args); System.out.println("結束JDK代理"); } return result; } }); //使用代理類調用方法 iud.find(); System.out.println("----------------"); iud.save(); } }
執行結果如下,
模擬查找用戶 ---------------- 開始JDK代理 模擬保存用戶 結束JDK代理
從上面可以看出代理類是由Proxy這個類通過newProxyInstance方法動態生成的,生成對象后使用“實例調用方法”的方式進行方法調用,那么代理類的被代理類的關系只有在執行這行代碼的時候才會生成,因此成為動態代理。
JDK的動態代理也存在不足,即被代理類必須要有實現的接口,如沒有接口則無法使用JDK動態代理(從newProxyInstance方法的第二個參數可得知,必須傳入被代理類的實現接口),那么需要使用CGLib動態代理。
四、CGLib動態代理
CGLib動態代理是一個第三方實現的動態代理類庫,不要求被代理類必須實現接口,它采用的是繼承被代理類,使用其子類的方式,彌補了被代理類沒有接口的不足,
要使用CGLib必須要引入第三方類庫,我這里使用的類庫如下,
要使用CGLib代理需要實現其MethodInterceptor接口,
package cn.com.cglib; import java.lang.reflect.Method; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class MyMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { // TODO Auto-generated method stub System.out.println("開始CGLib動態代理"); Object object=proxy.invokeSuper(obj, args); System.out.println("結束CGLib動態代理"); return object; } }
其,測試代碼如下,
package cn.com.cglib; import cn.com.staticProxy.UserDao; import net.sf.cglib.proxy.Enhancer; public class Test { public static void main(String[] args) { // TODO Auto-generated method stub Enhancer enhancer=new Enhancer(); enhancer.setSuperclass(UserDao.class); enhancer.setCallback(new MyMethodInterceptor()); //生成代理類 UserDao ud=(UserDao) enhancer.create(); ud.save();
System.out.println("----------------"); ud.find(); } }
可以看出使用Enhancer生成代理類,需要設置被代理類,也就是父類(從這里可以看出是使用繼承,生成的子類),設置回調方法。
開始CGLib動態代理
模擬保存用戶
結束CGLib動態代理
--------------------
開始CGLib動態代理
模擬查找用戶
結束CGLib動態代理
在設置回調enhancer.setCallback的時候需要傳入MethodInterceptor的實例,這里可以使用匿名內部類的方式,可參照上面的。
五、總結
對靜態代理、JDK動態代理、CGLib動態代理做一個總結,靜態代理的維護成本比較高,有一個被代理類就需要創建一個代理類,而且需要實現相同的接口。JDK動態代理模式和CGLib動態代理的區別是JDK動態代理需要被代理類實現接口,而CGLib則是生成被代理類的子類,要求被代理類不能是final的,因為final類無法被繼承。
下次會基於springAOP梳理相關內容。
有不正之處歡迎指正,感謝!