设计模式——动态代理

版权声明:转载请注明出处 https://blog.csdn.net/qq_39385118/article/details/83859043

1. 引言

动态代理的意义在于生成一个占位(又称代理对象),来代理真实对象,从而控制真实对象的访问,代理的作用就是在真实对象访问之前或者之后加入对应的逻辑,或者根据其他规则控制是否使用真实对象,所以代理必须实现两个步骤:

  • 代理对象和真实对象建立代理关系
  • 实现代理对象的代理逻辑方法

他的优点是可以隐藏真实类的实现,可以实现客户与委托类间的解耦,在不修改真实类代码的情况下能够做一些额外的处理。Java中最常用的动态代理技术有JDK动态代理和CGLIB,JDK必须使用接口而CGLIB不需要。

2. 静态代理

若代理类在程序运行前就已经存在,那么这种代理方式被称为静态代理,静态代理中的代理类和真实类会实现同一接口或是派生自相同的父类。下面我们用Vendor类代表生产厂家,BusinessAgent类代表微商代理,来介绍下静态代理的简单实现,委托类和代理类都实现了Sell接口,Sell接口的定义如下:

/**
 * 委托类和代理类都实现了Sell接口
 */
public interface Sell { 
    void sell(); 
    void ad(); 
} 

Vendor类的定义如下:

/**
 * 生产厂家
 */
public class Vendor implements Sell { 
    public void sell() { 
        System.out.println("In sell method"); 
    } 
    
    public void ad() { 
        System,out.println("ad method");
    }
} 

代理类BusinessAgent的定义如下:

/**
 * 代理类
 */
public class BusinessAgent implements Sell {
    private Sell vendor;
    
    public BusinessAgent(Sell vendor){
        this.vendor = vendor;
    }

    public void sell() { 
        vendor.sell();
    } 
    
    public void ad() {
        vendor.ad();
    }
} 

从BusinessAgent类的定义我们可以了解到,静态代理可以通过聚合来实现,让代理类持有一个委托类的引用即可。
下面我们考虑一下这个需求:给Vendor类增加一个过滤功能,只卖货给大学生。通过静态代理,我们无需修改Vendor类的代码就可以实现,只需在BusinessAgent类中的sell方法中添加一个判断即可如下所示:

/**
 * 代理类
 */
public class BusinessAgent(){ implements Sell {
    private Sell vendor;
    
    public BusinessAgent(Sell vendor){
        this.vendor = vendor;
    }

    public void sell() {
        if (isCollegeStudent()) {
            vendor.sell();
        }
    } 
    
    public void ad() {
        vendor.ad();
    }
} 

这样就可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。静态代理的局限在于运行前必须编写好代理类。

3. JDK动态代理

静态代理的代理关系在编译时就确定了,而动态代理的代理关系是在运行时确定的,代理对象是在程序运行时产生的,而不是编译期。
对代理对象的所有接口方法调用都会转发到InvocationHandler.invoke()方法,在invoke()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等,之后我们通过反射的方式执行真正的方法体。

3.1 定义接口

JDK动态代理是java.lang.reflect.*包提供的方式,他必须借助一个接口才能产生代理对象,所以先定义接口

/**
 * 使用jdk动态代理必须要使用接口
 * @author littlemotor
 *
 */
public interface HelloWorld {
  abstract public void sayHelloWorld();
}

3.2 实现类

public class HelloWorldImp implements HelloWorld{

  @Override
  public void sayHelloWorld() {
    System.out.println("Hello World!");
  }

}

3.3 动态代理绑定和代理逻辑实现

public class JdkProxy implements InvocationHandler{

  //真实对象
  private Object target = null;
  
  /**
   * 建立代理对象和真实对象的代理关系,并返回代理对象
   * @param target
   * @return 代理对象
   */
  public Object bind(Object target) {
    this.target = target;
    return Proxy.newProxyInstance(target.getClass().getClassLoader(),
        target.getClass().getInterfaces(), this);
  }
  
  /**
   *代理方法逻辑
   * @param proxy 代理对象
   * @param method 当前调度对象
   * @param args 当前方法参数
   * @return 代理结果返回
   * @throws Throwable
   */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("进入代理逻辑方法");
    System.out.println("调用真实对象之前的服务");
    Object obj = method.invoke(target, args);
    System.out.println("调用真实对象之后的服务");
    return obj;
  }
}

JDK动态代理总共分为两大步:

  • 建立代理对象和真实对象的关系
  • 实现代理逻辑方法

3.4 建立代理对象和真实对象的关系

这里使用bind方法完成,方法里首先用类的属性target保存了真实对象,然后通过如下代码建立并生成代理对象

Proxy.newProxyInstance(target.getClass().getClassLoader(),
        target.getClass().getInterfaces(), this);

newProxyInstance方法包含三个参数

  • 第1个是类加载器,我们采用target本身的类加载器
  • 第2个是把生成的动态代理对象下挂到哪些接口下面
  • 第3个是定义实现方法逻辑的代理类,这个this表示当前对象,他必须实现InvocationHandler接口的inoke方法

3.5 实现代理逻辑方法

invoke方法可以实现代理逻辑,invoke有三个参数含义如下:

  • proxy,代理对象,就是bind方法生成的对象
  • method,当天调度的方法
  • args,调度方法的参数
Object obj = method.invoke(target,args);

3.6 main方法

public class JdkProxyMain {
  public static void main(String[] args) {
    JdkProxy jdkProxy = new JdkProxy();
    //System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
    HelloWorld helloWorld = (HelloWorld) jdkProxy.bind(new HelloWorldImp());
    helloWorld.sayHelloWorld();
  }
}

//进入代理逻辑方法
//调用真实对象之前的服务
//Hello World!
//调用真实对象之后的服务

4. 总结

动态代理真正将代码中横向切面的逻辑剥离了出来,起到代码复用的目的。但是动态代理也有缺点,一是它的实现比静态代理更加复杂也不好理解;二是它存在一定的限制,例如它要求需要代理的对象必须实现了某个接口;三是某些方面也不够灵活,动态代理会为接口中的声明的所有方法添加上相同的代理逻辑。当然,这只是JDK动态代理所存在的一些缺陷,动态代理还有另外的实现如使用CGLIB库,在本文不做介绍,读者可以自行去了解。

参考:
《Java动态代理》https://juejin.im/post/5ad3e6b36fb9a028ba1fee6a#comment
JDK动态代理系列(劳夫子)https://www.cnblogs.com/liuyun1995/p/8144628.html

猜你喜欢

转载自blog.csdn.net/qq_39385118/article/details/83859043