Spring知識點總結(四)之SpringAOP基礎 - 代理設計模式
1. 分析程式中存在的問題(高內聚,低耦合)
通過springIOC DI) 以及註解的使用,成功解決了在程式中層與層之間出現的耦合的問題,但是在很多地方仍然存在非該層應該實現的功能,造成了 無法“高內聚”的現象,同時存在大量存在重複程式碼,開發效率低下。
1 @Service 2 public class UserServiceImpl implementsUserService { 3 @Autowired 4 private UserDao userDao; 5 6 @Override 7 public void registUser(User user) { 8 try { 9 System.out.println("校驗許可權。。。"); 10 System.out.println("開啟事務。。。");11 System.out.println("記錄日誌。。。"); 12 userDao.addUser(user); 13 System.out.println("提交事務。。。"); 14 } catch (Exception e) { 15 System.out.println("回滾事務"); 16 e.printStackTrace();17 } 18 } 19 20 @Override 21 public void upToVIP(User user) { 22 try { 23 System.out.println("校驗許可權。。。"); 24 System.out.println("開啟事務。。。"); 25 System.out.println("記錄日誌。。。"); 26 userDao.updateUser(user); 27 System.out.println("提交事務。。。"); 28 } catch (Exception e) { 29 System.out.println("回滾事務"); 30 e.printStackTrace(); 31 } 32 33 } 34 35 @Override 36 public void removeUser(User user) { 37 try { 38 System.out.println("校驗許可權。。。"); 39 System.out.println("開啟事務。。。"); 40 System.out.println("記錄日誌。。。"); 41 userDao.deleteUser(user.getId()); 42 System.out.println("提交事務。。。"); 43 } catch (Exception e) { 44 System.out.println("回滾事務"); 45 e.printStackTrace(); 46 } 47 } 48 49 } 50 此時可以通過代理設計模式,將這部分程式碼提取到代理者中,簡化層中的程式碼。 51 52 2. 靜態代理模式 53 package cn.tedu.staticproxy; 54 public interface SJSkill { 55 public void 吃(); 56 public void 唱歌(); 57 } 58 59 package cn.tedu.staticproxy; 60 public class FBB implements SJSkill{ 61 public void 吃(){ 62 System.out.println("fbb吃飯。。。"); 63 } 64 public void 唱歌(){ 65 System.out.println("fbb唱歌。。。"); 66 } 67 } 68 69 package cn.tedu.staticproxy; 70 public class JJRStaticProxy implements SJSkill{ 71 72 private FBB fbb = new FBB(); 73 74 @Override 75 public void 吃() { 76 System.out.println("許可權認證:你誰啊????"); 77 fbb.吃(); 78 System.out.println("記錄日誌:等我,我記一下來訪記錄"); 79 } 80 81 @Override 82 public void 唱歌() { 83 System.out.println("許可權認證:你誰啊????"); 84 fbb.唱歌(); 85 System.out.println("記錄日誌:等我,我記一下來訪記錄"); 86 } 87 88 } 89 90 package cn.tedu.staticproxy; 91 import org.junit.Test; 92 public class StaticProxyTest { 93 @Test 94 public void test01(){ 95 JJRStaticProxy jjr = new JJRStaticProxy(); 96 jjr.吃(); 97 jjr.唱歌(); 98 } 99 }
靜態代理設計模式特點:
優點:
結構清晰 易於理解
缺點:
如果被代理者有多個方法,則代理者也需要開發多個方法,其中往往存在大量重複程式碼,仍然存在程式碼重複。
靜態代理設計模式解決了軟體分層過程中 額外的功能程式碼侵入模組的問題,將額外的功能程式碼提取到了代理者中進行,但是靜態代理實現的代理者中存在大量重複的程式碼,並沒有解決程式碼重複問題。所以在真正開發中--包括spring的底層,基本不會使用靜態代理。
3. 動態代理 - jdk內建的動態代理
在jdk中提供了動態代理實現的工具類,直接使用該工具類就可以創建出代理者,並且可以通過內建的回撥函式指定代理在工作時的執行邏輯,從而實現基於jdk原生api的動態代理機制。
java.lang.reflect
類 Proxy
java.lang.Object
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
返回一個指定介面的代理類例項,該介面可以將方法呼叫指派到指定的調用處理程式。
案例:
1 package cn.tedu.javaproxy; 2 3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Method; 5 import java.lang.reflect.Proxy; 6 7 import org.junit.Test; 8 9 public class JavaProxyTest { 10 @Test 11 public void test01(){ 12 13 //被代理者 14 final FBB fbb = new FBB(); 15 16 //java動態代理方式 生成fbb的代理者 17 /** 18 * classLoader:用來生成代理者類的類載入器,通常可以傳入被代理者類的類載入器 19 * interfaces: 要求生成的代理者實現的介面們,通常就是實現和被代理者相同的介面,保證具有和被代理者相同的方法 20 * invocationHandler: 用來設定回撥函式的回撥介面,使用者需要寫一個類實現此介面,從而實現其中的invoke方法, 21 * 在其中編寫程式碼處理代理者呼叫方法時的回撥過程,通常在這裡呼叫真正物件身上的方法,並且在方法之前或之後做額外操作。 22 */ 23 SJSkill proxy = (SJSkill) Proxy.newProxyInstance(FBB.class.getClassLoader(),FBB.class.getInterfaces() 24 ,new InvocationHandler() { 25 @Override 26 /** 27 * proxy: 代理者 28 * method:當前呼叫的方法物件 由被代理的方法呼叫 29 * args:擋牆呼叫的方法的引數陣列 方法呼叫的引數 30 */ 31 public Object invoke(Object proxy, Method method, Object[] args)throws Throwable { 32 if("拍電影".equals(method.getName())){ 33 System.out.println("不好意思,給多少錢不拍了~~"); 34 return null; 35 }else{ 36 System.out.println("檢驗許可權。。。。"); 37 Object returnObj = method.invoke(fbb, args); 38 System.out.println("記錄日誌。。。。"); 39 return returnObj; 40 } 41 } 42 }); 43 //從此之後,不允許直接呼叫被代理者身上的方法,而是要通過代理者來呼叫 44 //fbb.吃(); 45 //fbb.唱歌(); 46 proxy.吃(); 47 proxy.唱歌(); 48 proxy.拍電影(); 49 } 50 }
java動態代理的原理圖:
java動態代理的特點:
優點:
不需要像靜態代理一樣被代理方法都要實現一遍,而只需要在回撥函式中進行處理就可以了,重複程式碼只需編寫一次。
缺點:
java的動態代理是通過代理者實現和被代理者相同的介面來保證兩者具有相同的方法的,如果被代理者想要被代理的方法不屬於任何介面,則生成的代理者自然無法具有這個方法,也就無法實現對該方法的代理。
所以java的動態代理機制是基於介面進行的,受制於要代理的方法是否有介面的支援。
4. 動態代理 - 第三方包cglib實現的動態代理
CGLIB是第三方提供的動態代理的實現工具,不管有沒有介面都可以實現動態代理。
CGLIB實現動態代理的原理是 生成的動態代理是被代理者的子類,所以代理者具有和父類即被代理者 相同的方法,從而實現代理。
a. 匯入CGLIB相關包
之前匯入的spring包中就包含了CGLIB
spring-core-3.2.3.RELEASE.jar
b. 開發CGLIB程式
案例:
1 package cn.tedu.cglibproxy; 2 import java.lang.reflect.Method; 3 import org.junit.Test; 4 import org.springframework.cglib.proxy.Enhancer; 5 import org.springframework.cglib.proxy.MethodInterceptor; 6 import org.springframework.cglib.proxy.MethodProxy; 7 8 public class CglibProxyTest { 9 @Test 10 public void test01(){ 11 final FBB fbb = new FBB(); 12 13 //增強器 14 Enhancer enhancer = new Enhancer(); 15 16 //設定介面 -- 此方法要求生成的動態代理額外實現指定介面們 ,單cglib動態代理不是靠介面實現的,所以可以不設定 17 enhancer.setInterfaces(fbb.getClass().getInterfaces()); 18 19 //設定父類 -- 此處要傳入被代理者的類,cglib是通過整合被代理者的類來持有和被代理者相同的方法的,此方法必須設定 20 enhancer.setSuperclass(fbb.getClass()); 21 22 //設定回撥函式 -- 為增強器設定回撥函式,之後通過增強器生成的代理物件呼叫任何方法都會走到此回撥函式中,實現呼叫真正被代理物件的方法的效果 23 enhancer.setCallback(new MethodInterceptor() { 24 @Override 25 public Object intercept(Object proxy, Method method, Object[] args, 26 MethodProxy methodProxy) throws Throwable { 27 if("拍電影".equals(method.getName())){ 28 System.out.println("對不起,不拍了~~~"); 29 return null; 30 }else{ 31 System.out.println("檢查許可權。。。"); 32 Object returnObj = method.invoke(fbb, args); 33 System.out.println("記錄日誌。。。"); 34 return returnObj; 35 } 36 } 37 }); 38 39 //生成代理物件 40 FBB proxy = (FBB) enhancer.create(); 41 proxy.吃(); 42 proxy.唱歌(); 43 proxy.拍電影(); 44 } 45 } 46
CGLIB動態代理原理圖:
CGLIB動態代理的特點:
優點:無論是否有介面都可以實現動態代理,使用場景基本不受限
缺點:第三方提供的動態代理機制,不是原生的,需要匯入第三方開發包才可以使用。