zoukankan      html  css  js  c++  java
  • 代理模式详解:静态代理、JDK动态代理与Cglib动态代理

    1. 代理模式简介分类

      • 概念

        ​ 代理,是为了在不修改目标对象的基础上,增强目标方法的业务逻辑。

        ​ 客户类需要执行的是目标对象的目标方法,但是真正执行的是代理对象的代理方法,客户类对目标对象的访问是通过代理对象来实现的。当然,代理类与目标类需要实现同一个接口。

      • 举例

        生活中遇到了官司,我们平常老百姓对法律的了解不全面,所以一般都会请律师处理。

        目标对象:法庭上我们一般称为当事人即目标对象

        代理类:律师称为代理律师即代理类

        共同接口:都为了一个共同的目标努力赢得官司即为共同接口

        目标方法:我们所做的提供证据各种努力成为目标方法

        代理方法:再此过程中我们可能做不全面,律师对证据材料等进行整理收集,结合法律法规进行辩证等等,此过程称为代理方法

      • 代理分类

        代理模式一般分为静态代理与动态代理,动态代理又分为JDK动态代理与CGLIB动态代理

    2. 静态代理

      • 概念

        静态代理是指,代理类在程序运行前就已经定义好,其与目标类等关系在程序运行前就已经确立。

        静态代理类似于富翁于私人律师的代理关系,并不是在发生官司之后才去请律师,而是在此之前已经确立好的代理关系。

      • 实现与解析

        a、定义业务接口

        package com.rangers.proxy.staticProxy;
        
        /**
         * @Author Rangers
         * @Description
         * @Date 2021-03-09
         **/
        public interface IAccountService {
            // 转账业务接口
            void transfer();
        }
        

        b、定义目标类与目标方法

        package com.rangers.proxy.staticProxy;
        
        /**
         * @Author Rangers
         * @Description
         * @Date 2021-03-09
         **/
        public class AccountServiceImpl implements IAccountService {
            // 转账业务实现即目标方法
            @Override
            public void transfer() {
                System.out.println("进行转账操作");
            }
        }
        

        c、定义代理类AccountServiceImplProxy,实现IAccountService接口。在有参构造方法中传入目标对象,将目标对象引入代理类,以便代理类调用目标方法,进行增强

        package com.rangers.proxy.staticProxy;
        
        /**
         * @Author Rangers
         * @Description
         * @Date 2021-03-09
         **/
        public class AccountServiceImplProxy implements IAccountService {
        
            // 声明目标接口对象
            private IAccountService target;
        
            public AccountServiceImplProxy() {
            }
            // 业务接口对象作为构造器,用于接收目标对象
            public AccountServiceImplProxy(IAccountService target) {
                this.target = target;
            }
        
            @Override
            public void transfer() {
                // 此处对目标方法进行增强
                System.out.println("对转账人身份校验。。");
                target.transfer();
                System.out.println("进行日志记录。。");
            }
        }
        

        d、编写测试类TransferServiceTest

        package com.rangers.proxy.staticProxy;
        
        /**
         * @Author Rangers
         * @Description
         * @Date 2021-03-09
         **/
        public class TransferServiceTest {
            public static void main(String[] args) {
                // 创建目标对象
                IAccountService target = new AccountServiceImpl();
                // 创建代理对象,传入目标对象进行初始化
                IAccountService proxy = new AccountServiceImplProxy(target);
                // 执行代理对象的方法
                proxy.transfer();
            }
        }
        
    3. 动态代理

      ​ 动态代理,程序在整个运行过程中根本就不存在目标类的代理类,目标对象的代理对象只是由代理工具在程序运行时由JVM根据反射等机制动态生成。代理对象与目标对象的代理关系在程序运行时才确立。

      ​ 动态代理类似于普通人在有官司之后,再聘请律师的,即代理关系是在官司发生后确立的。

      ​ 动态代理的实现方式有两种:JDK的动态代理、CGLIB动态代理

      a、JDK动态代理

      • 概念

        ​ JDK动态代理是通过JDK提供的 java.lang.reflect.Proxy类实现动态大力,使用其静态方法newProxyInstance(),对目标对象、业务接口及业务增强逻辑,自动生成一个动态代理对象。

        public static newProxyInstance(ClassLoader classLoader,Class<?> interfaces,InvocationHandler handler)
          classLoader:传入目标类的类加载器,通过目标对象的反射获取
          interfaces:目标对象实现的接口数组,通过目标对象的反射获取
        	handler:业务增强逻辑,需要具体实现
        

        ​ InvocationHandler是个接口,实现InvocationHandler接口的类用于增加目标类的业务逻辑。需要实现invoke()方法,具体的增强逻辑就是在此方法中进行实现,程序调用住业务逻辑时会自动调用invoke()方法

        public Object invoke(Object proxy,Method method,Object[] args)
          proxy:生成的代理对象
        	method:目标方法
        	args:目标方法的参数
        

        ​ Method类对象,invoke()方法进行执行目标对象的目标方法

        public Object invoke(Object obj,Object args)
            method.invoke(Object target,Object...args)执行目标方法
            target:目标对象
            args:目标方法的执行参数
        
      • 实现与解析

        1. 定义业务接口与实现类
        package com.rangers.proxy.jdkProxy;
        
        /**
         * @Author Rangers
         * @Description
         * @Date 2021-03-09
         **/
        public interface IAccountService {
            // 转账业务接口
            void transfer();
        }
        
        package com.rangers.proxy.jdkProxy;
        
        /**
         * @Author Rangers
         * @Description
         * @Date 2021-03-09
         **/
        public class AccountServiceImpl implements IAccountService {
            // 转账业务实现即目标方法
            @Override
            public void transfer() {
                System.out.println("进行转账操作");
            }
        }
        
        1. 定义JdkProxy类实现InvocationHandler接口,实现invoke()方法,并对业务逻辑进行增强
        package com.rangers.proxy.jdkProxy;
        
        import java.lang.reflect.InvocationHandler;
        import java.lang.reflect.Method;
        
        /**
         * @Author Rangers
         * @Description
         * @Date 2021-03-09
         **/
        public class JdkProxy implements InvocationHandler {
        
            private Object target;
        
            public JdkProxy() {
            }
        
            public JdkProxy(Object target) {
                this.target = target;
            }
        
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 此处对目标方法进行增强
                System.out.println("对转账人身份校验。。");
                Object result = method.invoke(target, args);
                System.out.println("进行日志记录。。");
                return result;
            }
        }
        
        1. 新建测试类JDKProxyTest
        package com.rangers.proxy.jdkProxy;
        
        import java.lang.reflect.InvocationHandler;
        import java.lang.reflect.Method;
        import java.lang.reflect.Proxy;
        
        /**
         * @Author Rangers
         * @Description
         * @Date 2021-03-09
         **/
        public class JDKProxyTest {
            public static void main(String[] args) {
                // 创建目标对象
                IAccountService target = new AccountServiceImpl();
                // 创建代理对象,传入目标对象进行初始化
                IAccountService proxyService =
                        (IAccountService) Proxy.newProxyInstance(
                                                            target.getClass().getClassLoader(),
                                                            target.getClass().getInterfaces(),
                                                            new JdkProxy(target)
                                                        );
                        // 亦可使用匿名类进行实现
                        /*(IAccountService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                                target.getClass().getInterfaces(), new InvocationHandler() {
                                    @Override
                                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                                        // 此处对目标方法进行增强
                                        System.out.println("对转账人身份校验。。");
                                        Object result = method.invoke(target, args);
                                        System.out.println("进行日志记录。。");
                                        return result;
                                    }
                                });*/
                // 此处执行的业务方法就是代理对象的增强过的逻辑
                proxyService.transfer();
            }
        }
        

      注:使用JDK动态代理时需要目标类目标方法必须在实现的接口中,否则不能使用此方式进行动态打击。对于无接口的类需要实现动态代理,就要使用CGLIB方式来进行实现

      b、CGLIB动态代理

      • 概念

        ​ CGLIB是一个开源的第三方代码生成类库,对于无接口的类,要为其创建动态代理,就要使用CGLIB进行实现。CGLIB代理的生成原理是生成目标类的子类,子类是增强过的,就是目标类的代理类。所以,使用CGLIB生成动态代理,要求目标类必须能够被继承,即不能是final修饰的类。

        ​ CGLIB包的底层是通过使用一个小儿快的字节码处理框架ASM(java字节码操控框架),来转换字节码并生成新的类,通过对字节码进行增强来生成代理类。

        ​ 我们静态代理理解为私人律师,JDK动态代理成为代理律师,CGLIB动态代理可以理解为老父亲的儿子。老父亲是被需要增强对目标类,儿子则是用于增强父亲对代理类,事先不需要约定。父亲需要儿子增强什么,儿子就增强什么,即他们之间的关系不要接口来进行约束。

      • 注意要点

        使用CGLIB动态代理时,生成代理类的类需要实现MethodInterceptor接口及intercept()方法

        public Object intercept(Object proxy,Method method,Objectp[] args,MethodProxy methodProxy)
          proxy:代理对象
          method:代理对象的方法,即增强后的方法
          args:方法参数
          methodProxy:代理方法的对象
        

        创建代理对象时使用Enhancer类

        // 创建增强器
        Enhancer enhancer = new Enhancer();
        // 初始化增强器:将目标类指定为父类
        enhancer.setSuperclass(target.class);
        // 初始化增强器:设置回调至本类中的intercept()方法
        enhancer.setCallback(this);
        // 使用增强器创建代理对象进行返回
        enhancer.create();
        
      • 实现与解析

        1. 引入CGLIB依赖

              <dependency>
                  <groupId>cglib</groupId>
                  <artifactId>cglib-full</artifactId>
                  <version>2.0.2</version>
              </dependency>
          
        2. 创建目标类AccountService

          package com.rangers.proxy.cglibProxy;
          
          /**
           * @Author Rangers
           * @Description
           * @Date 2021-03-09
           **/
          public class AccountService  {
          
              // 转账业务 即目标方法
              public void transfer() {
                  System.out.println("进行转账操作");
              }
          
              // 查询余额 即目标方法
              public void getBalance() {
                  System.out.println("查询余额操作");
              }
          }
          
        3. 创建代理类AccountServiceCglibProxy实现MethodInterceptor接口,完善intercept()方法进行增强,创建生成代理对象createProxy()方法

          package com.rangers.proxy.cglibProxy;
          
          import net.sf.cglib.proxy.Enhancer;
          import net.sf.cglib.proxy.MethodInterceptor;
          import net.sf.cglib.proxy.MethodProxy;
          
          import java.lang.reflect.Method;
          
          /**
           * @Author Rangers
           * @Description
           * @Date 2021-03-09
           **/
          public class AccountServiceCglibProxy implements MethodInterceptor {
          
              // 声明目标类的成员变量,并创建以目标类为参数的构造器,用于接收目标对象
              private AccountService target;
          
              public AccountServiceCglibProxy(AccountService accountService) {
                  this.target = accountService;
              }
          
              @Override
              public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                  // 此处对目标方法进行增强
                  Object result = new Object();
                  if ("transfer".equals(method.getName())){
                      System.out.println("对转账人身份校验。。");
                      result = method.invoke(target, args);
                      System.out.println("进行日志记录。。");
                  }else{
                      // 直接执行目标对象的目标方法
                      result = methodProxy.invokeSuper(target,args);
                  }
                  return result;
              }
          
              // 创建代理对象
              public AccountService createProxy(){
                  // 创建增强器
                  Enhancer enhancer = new Enhancer();
                  // 初始化增强器:将目标类指定为父类
                  enhancer.setSuperclass(AccountService.class);
                  // 初始化增强器:设置回调至本类中的intercept()方法
                  enhancer.setCallback(this);
                  // 使用增强器创建代理对象
                  return (AccountService) enhancer.create();
              }
          }
          
        4. 创建测试类CglibProxyTest

          package com.rangers.proxy.cglibProxy;
          
          /**
           * @Author Rangers
           * @Description
           * @Date 2021-03-09
           **/
          public class CglibProxyTest {
              public static void main(String[] args) {
                  // 目标对象
                  AccountService target = new AccountService();
                  // 创建代理对象,传入目标对象进行初始化
                  AccountService accountService = new AccountServiceCglibProxy(target).createProxy();
                  accountService.transfer();
                  accountService.getBalance();
              }
          }
          
  • 相关阅读:
    为什么要使用href=”javascript:void(0);”
    29zTree
    js获取下拉框的值
    C# 判断文件夹与文件是否存在
    C# 判断文件和文件夹是否存在并创建
    ASP.NET实现excel导入数据库
    同时向主表和从表里面导入execl数据 (asp.net webform)
    EasyUI数据分页实现(真假分页)
    easyui datagrid 前台分页的实现java采用的版本
    异步和多线程
  • 原文地址:https://www.cnblogs.com/rangers-sun/p/14506262.html
Copyright © 2011-2022 走看看