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