Java代理模式及动态代理详解

Java的动态代理在实践中有着普遍的使用场景,好比最场景的Spring AOP、Java注解的获取、日志、用户鉴权等。本篇文章带你们了解一下代理模式、静态代理以及基于JDK原生动态代理。java

代理模式

不管学习静态代理或动态代理,咱们都要先了解一下代理模式。编程

先看百度百科的定义:微信

代理模式的定义:为其余对象提供一种代理以控制对这个对象的访问。在某些状况下,一个对象不适合或者不能直接引用另外一个对象,而代理对象能够在客户端和目标对象之间起到中介的做用。

直接看定义可能有些难以理解,咱们就以生活中具体的实例来讲明一下。框架

咱们都去过超市购买过物品,超市从厂商那里购买货物以后出售给咱们,咱们一般并不知道货物从哪里通过多少流程才到超市。ide

在这个过程当中,等因而厂商“委托”超市出售货物,对咱们来讲是厂商(真实对象)是不可见的。而超市(代理对象)呢,做为厂商的“代理者”来与咱们进行交互。函数

同时,超市还能够根据具体的销售状况来进行折扣等处理,来丰富被代理对象的功能。学习

经过代理模式,咱们能够作到两点:测试

一、隐藏委托类的具体实现。this

二、实现客户与委托类的解耦,在不改变委托类代码的状况下添加一些额外的功能(日志、权限)等。spa

代理模式角色定义

在上述的过程当中在编程的过程当中咱们能够定义为三类对象:

  • Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法。好比:广告、出售等。
  • RealSubject(真实主题角色):真正实现业务逻辑的类。好比实现了广告、出售等方法的厂家(Vendor)。
  • Proxy(代理主题角色):用来代理和封装真实主题。好比,一样实现了广告、出售等方法的超时(Shop)。

以上三个角色对应的类图以下:

Java代理及动态代理详解

静态代理实例

静态代理是指代理类在程序运行前就已经存在,这种状况下的代理类一般都是咱们在Java代码中定义的。

下面咱们就以具体的实例来演示一下静态代理。

首先定义一组接口Sell,用来提供广告和销售等功能。而后提供Vendor类(厂商,被代理对象)和Shop(超市,代理类),它们分别实现了Sell接口。

Sell接口定义以下:

/**
 * 委托类和代理类都实现了Sell接口
 * @author sec
 * @version 1.0
 * @date 2020/3/21 9:30 AM
 **/
public interface Sell {

    /**
     * 出售
     */
    void sell();

    /**
     * 广告
     */
    void ad();
}

Vendor类定义以下:

/**
 * 供应商
 * @author sec
 * @version 1.0
 * @date 2020/3/21 9:30 AM
 **/
public class Vendor implements Sell{

    @Override
    public void sell() {
        System.out.println("Shop sell goods");
    }

    @Override
    public void ad() {
        System.out.println("Shop advert goods");
    }
}

Shop类定义以下:

/**
 * 超市,代理类
 * @author sec
 * @version 1.0
 * @date 2020/3/21 9:30 AM
 **/
public class Shop implements Sell{

    private Sell sell;

    public Shop(Sell sell){
        this.sell = sell;
    }

    @Override
    public void sell() {
        System.out.println("代理类Shop,处理sell");
        sell.sell();
    }

    @Override
    public void ad() {
        System.out.println("代理类Shop,处理ad");
        sell.ad();
    }
}

其中代理类Shop经过聚合的方式持有了被代理类Vendor的引用,并在对应的方法中调用Vendor对应的方法。在Shop类中咱们能够新增一些额外的处理,好比筛选购买用户、记录日志等操做。

下面看看在客户端中如何使用代理类。

/**
 * 静态代理类测试方法
 * @author sec
 * @version 1.0
 * @date 2020/3/21 9:33 AM
 **/
public class StaticProxy {

    public static void main(String[] args) {

        // 供应商---被代理类
        Vendor vendor = new Vendor();

        // 建立供应商的代理类Shop
        Sell sell = new Shop(vendor);

        // 客户端使用时面向的是代理类Shop。
        sell.ad();
        sell.sell();
    }
}

在上述代码中,针对客户看到的是Sell接口提供了功能,而功能又是由Shop提供的。咱们能够在Shop中修改或新增一些内容,而不影响被代理类Vendor。

静态代理的缺点

静态代理实现简单且不侵入原代码,但当场景复杂时,静态代理会有如下缺点:

一、当须要代理多个类时,代理对象要实现与目标对象一致的接口。要么,只维护一个代理类来实现多个接口,但这样会致使代理类过于庞大。要么,新建多个代理类,但这样会产生过多的代理类。

二、当接口须要增长、删除、修改方法时,目标对象与代理类都要同时修改,不易维护。

因而,动态代理便派上用场了。

动态代理

动态代理是指代理类在程序运行时进行建立的代理方式。这种状况下,代理类并非在Java代码中定义的,而是在运行时根据Java代码中的“指示”动态生成的。

相比于静态代理,动态代理的优点在于能够很方便的对代理类的函数进行统一的处理,而不用修改每一个代理类的函数。

基于JDK原生动态代理实现

实现动态代理一般有两种方式:JDK原生动态代理和CGLIB动态代理。这里,咱们以JDK原生动态代理为例来进行讲解。

JDK动态代理主要涉及两个类:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。

InvocationHandler接口定义了以下方法:

/**
 * 调用处理程序
 */
public interface InvocationHandler { 
    Object invoke(Object proxy, Method method, Object[] args); 
}

顾名思义,实现了该接口的中介类用作“调用处理器”。当调用代理类对象的方法时,这个“调用”会转送到invoke方法中,代理类对象做为proxy参数传入,参数method标识了具体调用的是代理类的哪一个方法,args为该方法的参数。这样对代理类中的全部方法的调用都会变为对invoke的调用,能够在invoke方法中添加统一的处理逻辑(也能够根据method参数对不一样的代理类方法作不一样的处理)。

Proxy类用于获取指定代理对象所关联的调用处理器。

下面以添加日志为例来演示一下动态代理。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;

public class LogHandler implements InvocationHandler {
    Object target;  // 被代理的对象,实际的方法执行者

    public LogHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);  // 调用 target 的 method 方法
        after();
        return result;  // 返回方法的执行结果
    }
    // 调用invoke方法以前执行
    private void before() {
        System.out.println(String.format("log start time [%s] ", new Date()));
    }
    // 调用invoke方法以后执行
    private void after() {
        System.out.println(String.format("log end time [%s] ", new Date()));
    }
}

客户端编写程序使用动态代理代码以下:

import java.lang.reflect.Proxy;

/**
 * 动态代理测试
 *
 * @author sec
 * @version 1.0
 * @date 2020/3/21 10:40 AM
 **/
public class DynamicProxyMain {

    public static void main(String[] args) {
        // 建立中介类实例
        LogHandler logHandler = new LogHandler(new Vendor());
        // 设置该变量能够保存动态代理类,默认名称$Proxy0.class
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

        // 获取代理类实例Sell
        Sell sell = (Sell) (Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[]{Sell.class}, logHandler));

        // 经过代理类对象调用代理类方法,实际上会转到invoke方法调用
        sell.sell();
        sell.ad();
    }
}

执行以后,打印日志以下:

调用方法sell之【前】的日志处理
Shop sell goods
调用方法sell之【后】的日志处理
调用方法ad之【前】的日志处理
Shop advert goods
调用方法ad之【后】的日志处理

通过上述验证,咱们发现已经成功为咱们的被代理类统一添加了执行方法以前和执行方法以后的日志。

在上述实例中为了看一下生成的动态代理类的代码,咱们添加了下面的属性设置(在生产环境中须要去掉该属性)。

// 设置该变量能够保存动态代理类,默认名称$Proxy0.class
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

那么,咱们能够执行main方法以后,还生成了一个名字为$Proxy0.class类文件。经过反编译可看到以下的代码:

package com.sun.proxy;

import com.choupangxia.proxy.Sell;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Sell {
    private static Method m1;
    private static Method m2;
    private static Method m4;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void ad() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void sell() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("com.choupangxia.proxy.Sell").getMethod("ad");
            m3 = Class.forName("com.choupangxia.proxy.Sell").getMethod("sell");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

能够看到$Proxy0(代理类)继承了Proxy类,而且实现了被代理的全部接口,以及equals、hashCode、toString等方法。

因为动态代理类继承了Proxy类,因此每一个代理类都会关联一个InvocationHandler方法调用处理器。

类和全部方法都被public final修饰,因此代理类只可被使用,不能够再被继承。

每一个方法都有一个Method对象来描述,Method对象在static静态代码块中建立,以“m+数字”的格式命名。

调用方法的时候经过super.h.invoke(this,m1,(Object[])null);调用。其中的super.h.invoke其实是在建立代理的时候传递给Proxy.newProxyInstance的LogHandler对象,它继承InvocationHandler类,负责实际的调用处理逻辑。

小结

关于代理和动态代理相关的内容,咱们就讲这么多。了解了代理模式可让咱们的系统设计的更加具备可扩展性。而动态代理的应用就更广了,各种框架及业务场景都在使用。有了两个基础,就可以更好的学习其余框架。

关于CGLIB动态代理的内容,咱们下篇文章再来聊一聊。


<center>程序新视界:精彩和成长都不容错过</center>

程序新视界-微信公众号

相关文章
相关标签/搜索