當前位置:網站首頁>代理模式

代理模式

2021-08-19 19:15:00 Aj小菜

代理模式

定義:

為實際對象提供一個代理,以控制對實際對象的訪問。代理類負責為委托類預處理消息,過濾消息並轉發消息,以及進行消息被委托執行後的後續處理。

image

一、靜態代理

廢話不多說,上例子。現在業務要求實現對學生信息的新增和修改功能,這個功能交到了小明的稱手上,而小明很快的完成了代碼的開發:

1、首先編寫了一個接口:

public interface StudentService {
    
    public int addStudent(Student student);
    
    public int updateStudent(Student student);
}

2、然後再編寫接口的實現類

public class StudentServiceImpl implements StudentService {
    
    public int addStudent(Student student) {
        // TODO 在這裏實現了把學生的信息增加到數據庫中
        System.out.printLn("在這裏實現了把學生的信息增加到數據庫中");
        // 偽代碼
        int retult = studentRepository.insertStudent(student);
    }
    
    public int updateStudent(Student student) {
        // TODO 在這裏實現了把學生的信息更新到數據庫中
        System.out.printLn("在這裏實現了把學生的信息更新到數據庫中");
        // 偽代碼
        int retult = studentRepository.updateStudent(student);
    }
}

寫完之後就可以給到調用層調用了。

因為這個功能,小明完成得非常的好,還拿了年度最佳員工大獎。

過了不久,上級領導來審查代碼,發現小明這個功能代碼竟然沒有記錄日志,這是個大問題。於是小明就想立刻的在StudentServiceImpl的實現類裏面增加上日志。但是這個領導又說了,不能在StudentServiceImpl類裏面修改。於是這個時候,小明熟悉的靜態代理模式就上場了。

3、使用靜態代理模式來進行代碼的增强,創建一個代理類

代理、代理,既然是代理,那麼這個代理類肯定是和幕後的執行者存在一定的關聯的,幕後執行者有的功能,代理類也要有,如果沒有,那就不叫代理類了。

  • 在這裏,幕後的執行者是StudentService接口,此接口有新增和修改Student信息的功能,所以我們的代理類也要有這兩個接口。那代理類如何有相同的增加和修改學生信息的功能呢?答案就是代理類也實現StudentService接口。

    //實現StudentService接口以獲得相同的實現方法,但是這裏不是做為實際的業務處理。
    public class StudentServiceProxy implements StudentService {
        
        public int addStudent(Student student) {
            
        }
        
        public int updateStudent(Student student) {
            
        }
    }
    
  • 這樣我們得到的第一步,但是這個代理類不是真正的業務實現類,它是要把業務邏輯的操作轉發給真正的執行者。很顯然,現在還沒有這個執行者,所以第二步,把執行者加上。

所以我們這樣來創建:

//實現StudentService接口以獲得相同的實現方法,但是這裏不是做為實際的業務處理。
public class StudentServiceProxy implements StudentService {
    
    //聲明實際的業務處理者
    private StudentService studentService;
    //構造器注入後,此代理類StudentServiceProxy就可以在自身調用studentService的實現業務處理方法,
    //就具有了代理的功能了
    public StudentServiceProxy(StudentService studentService) {
        this.studentService = studentService;
    }
    
    public int addStudent(Student student) {
        //代理類做的事情(增强)
        beforeLog(student);
        
        //真正做業務處理的幕後者
        studentService.addStudent(student);
        
        //代理類做的事情(增强)
        afterLog(student);
    }
    
    public int updateStudent(Student student) {
        //代理類做的事情(增强)
        beforeLog(student);
        
        //真正做業務處理的幕後者
        studentService.updateStudent(student);
        
        //代理類做的事情(增强)
        afterLog(student);
    }
    
    //假設正面是一些日志的方法(代理類增加的一些功能)
    private void beforeLog(Student student) {
        System.out.println("請求的參數:" + studetn.toString);
        System.out.println(String.format("請求開始的時間:", new Date()));
    }
    
    private void afterLog(Student student) {
        System.out.println(String.format("請求結束的時間:", new Date()));
    }
    
}

4、調用層調用

public class StudentController {
    
    public static void main(String[] args) {
        StudentServiceImpl studentServiceImpl = new StudentServiceImpl();
        StudentServiceProxy proxy = new StudentServiceProxy(studentServiceImpl);

        proxy.addStudent();
        proxy.updateStudent();
    }
    
}

5、總結

由上面的例子可以知道,真正的業務實現類是沒有日志的功能的。日志的功能是在代理類裏面增加的。且調用層也沒有真正的去調用真正的業務實現類,而是調用了它的代理類來。可以看出,代理模式的主要的特點就是不修改原有的業務邏輯而可以對業務類進行其他功能的增加。如:日志的輸出、參數的校驗、根據返回的結果做相應的處理......

二、動態代理

聰明的小明發現了靜態代理模式的一些缺點:

  • 當接口增加或者删除一些接口時,代理類也要同時的進行增加或者修改,不易維護;
  • 如果一個接口對應的創建一個代理類時,代理類就會過多,也不方便維護;
  • 如果所有的接口都共用一個代理類,則這個代理類就會顯得過於龐大;

那有什麼更好的方法來優化呢?有,就是今天的主角:動態代理

最常用的動態代理的方法有兩種:

1、通過實現接口的方式——JDK動態代理

JDK動態代理主要涉及兩個類:java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler。

針對上面的靜態代理,我們如何使用動態代理來進行增加日志的功能呢?

  • 創建一個代理類LogHandler,這個代理類實現了接口InvocationHandler(靜態代理類是實現了其他具體的業務接口),在聲明一個幕後的執行者(實際的業務處理類)Object,沒錯,就是Object,萬物皆Object。

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    //實現InvocationHandler接口
    public class LogHandler implements InvocationHandler {
        //實際的業務執行者
        private Object target;
        //構造注入
        public LogHandler(Object obj) {
            this.target = obj;
        }
        //實現 InvocationHandler接口中的方法
        //proxy:代錶動態代理對象
        //method:代錶正在執行的方法
        //args:代錶調用目標方法時傳入的實參
    	@Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
            beforeLog(args);
            //真正的執行者執行業務邏輯,返回結果
            Object result = method.invoke(target, args); 
            afterLog();
        }
        
        //假設下面是一些日志的方法(代理類增加的一些功能)
        private void beforeLog(Object[] args) {
            if (args.length > 0) {
                for (int i = 0; i < args.length; i++) {
                    System.out.println("請求的參數:" + args[i].toString());
                }
            }
            System.out.println("請求開始的時間:" + new Date());
        }
    
        private void afterLog() {
            System.out.println("請求結束的時間:" + new Date());
        }
    }
    
  • 調用

    public class StudentController {
        
        public static void main(String[] args) {
            //要求一個真正的執行者
            StudentService studentService = new StudentServiceImpl();
            //創建一個InvocationHandler,並關聯了真正的業務執行者studentService
            InvocationHandler logHandler = new LogHandler(studentService);
            
            //獲取具體業務實現類的Class
            Class<?> studentServiceClass = studentService.getClass();
            
            //創建一個動態的代理類
            //參數1:loader:具體業務實現類的類加載器
            //參數2:interface[]:具體業務實現類的接口,是一個數組,代理類會動態的實現這些接口後,即可對外調用。
            //參數3:InvocationHandler 錶示的是當動態代理對象調用方法的時候會關聯到哪一個InvocationHandler對象上,並最終由其調用
            StudentService studentServicerProxy = (StudentService) Proxy.newProxyInstance(studentServiceClass.getClassLoader(), studentServiceClass.getInterfaces(), logHandler);
            Student s = new Student();
            
            //通過動態代理類來調用新增、修改學生的方法
            studentServicerProxy.addStudent(s);
            studentServicerProxy.updateStudent(s);
        }
        
    }
    

image

2、通過繼承類的方式——CGLIB動態代理

JDK實現動態代理是通過接口定義業務方法,對於一些沒有接口的類,如果要實現動態代理,則需要到CGLIB了。其采用了底層的字節碼技術,其原理是通過字節碼技術為一個類創建子類,並在子類中采用方法攔截的技術攔截所有的父類方法的調用,織入橫切。但是因為采用的是繼承,所以不能對final修飾的類進行代理。JDK動態代理與CGLIB動態代理均是實現Spring AOP的基礎。CGlib不是java自帶的API,所以要引入cglib的jar包。

  • 創建一個類,其沒有接口的實現

    public class StudentServiceImpl {
        
        public int addStudent(Student student) {
            // TODO 在這裏實現了把學生的信息增加到數據庫中
            System.out.printLn("在這裏實現了把學生的信息增加到數據庫中");
            // 偽代碼
            int retult = studentRepository.insertStudent(student);
        }
        
        public int updateStudent(Student student) {
            // TODO 在這裏實現了把學生的信息更新到數據庫中
            System.out.printLn("在這裏實現了把學生的信息更新到數據庫中");
            // 偽代碼
            int retult = studentRepository.updateStudent(student);
        }
    }
    
  • 編寫一個LogInterceptor類,繼承MethodInterceptor

    public class LogInterceptor implements MethodInterceptor {
     	/**
         * @param object 錶示要進行增强的對象
         * @param method 錶示攔截的方法
         * @param objects 數組錶示參數列錶,基本數據類型需要傳入其包裝類型,如int-->Integer、long-Long、double-->Double
         * @param methodProxy 錶示對方法的代理,invokeSuper方法錶示對被代理對象方法的調用
         * @return 執行結果
         * @throws Throwable
         */
        @Override
        public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            beforeLog(objects);
            //注意這裏是調用 invokeSuper 而不是 invoke,否則死循環,methodProxy.invokesuper執行的是原始類的方法,method.invoke執行的是子類的方法
            Object result = methodProxy.invokeSuper(object, objects);   
            afterLog();
            return result;
        }
        //假設下面是一些日志的方法(代理類增加的一些功能)
        private void beforeLog(Object[] args) {
            if (args.length > 0) {
                for (int i = 0; i < args.length; i++) {
                    System.out.println("請求的參數:" + args[i].toString());
                }
            }
            System.out.println("請求開始的時間:" + new Date());
        }
    
        private void afterLog() {
            System.out.println("請求結束的時間:" + new Date());
        } 
    }
    
  • 調用

    class LogInterceptorController {
    
        public static void main(String[] args) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(StudentServiceImpl.class);
            enhancer.setCallback(new LogInterceptor());
            //創建代理類
            StudentServiceImpl stImpl = (StudentServiceImpl) enhancer.create();
            Student s = new Student();
            s.setStName("張三");
            stImpl.updateStudent(s);
        }
    }
    
  • 執行結果

image

可以看到,這個是和JDK動態代理實現的效果是一樣的

  • 此外,還可以進一步多個 MethodInterceptor 進行過濾篩選,我們再創建一個LogInterceptor1,在日志的輸入上做區分。

    public class LogInterceptor1 implements MethodInterceptor {
    
        @Override
        public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            beforeLog(objects);
            //注意這裏是調用 invokeSuper 而不是 invoke,否則死循環,methodProxy.invokesuper執行的是原始類的方法,
            // method.invoke執行的是子類的方法
            Object result = methodProxy.invokeSuper(object, objects);
            afterLog();
            return result;
        }
    
        //假設下面是一些日志的方法(代理類增加的一些功能)
        private void beforeLog(Object[] args) {
            if (args.length > 0) {
                for (int i = 0; i < args.length; i++) {
                    System.out.println("請求的參數1:" + args[i].toString());
                }
            }
            System.out.println("請求開始的時間1:" + new Date());
        }
    
        private void afterLog() {
            System.out.println("請求結束的時間1:" + new Date());
        }
    }
    
  • 再創建一個選擇器

    public class StudnetCallbackFilter implements CallbackFilter {
    
        @Override
        public int accept(Method method) {
            //新增,使用LogInterceptor代理
            if ("addStudent".equals(method.getName())) {
                return 0;
            }
            //updateStudent,使用LogInterceptor1代理
            if ("updateStudent".equals(method.getName())) {
                return 1;
            }
            return 1;
        }
    }
    
  • 再進行測試

    class LogInterceptorController {
    
        public static void main(String[] args) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(StudentServiceImpl.class);
    //        enhancer.setCallback(new LogInterceptor());
            enhancer.setCallbacks(new Callback[]{new LogInterceptor(), new LogInterceptor1(), NoOp.INSTANCE});   // 設置多個攔截器,NoOp.INSTANCE是一個空攔截器,不做任何處理
            enhancer.setCallbackFilter(new StudnetCallbackFilter());
            //創建代理類
            StudentServiceImpl stImpl = (StudentServiceImpl) enhancer.create();
            Student s = new Student();
            s.setStName("張三");
            stImpl.addStudent(s);
            System.out.println("================================");
            stImpl.updateStudent(s);
        }
    }
    

image

總結

靜態代理

​ 不同的接口要有不同的代理類實現,會很冗餘;

JDK動態代理

​ 解决了靜態代理中冗餘的代理實現類問題,JDK 動態代理是基於接口設計實現的,如果被代理對象沒有實現接口,會拋出异常。

CGLIB動態代理

​ 沒有接口也能實現動態代理,而且采用字節碼增强技術。技術實現相對難理解些

版權聲明
本文為[Aj小菜]所創,轉載請帶上原文鏈接,感謝
https://cht.chowdera.com/2021/08/20210819191500435p.html

隨機推薦