JAVA 代理模式

简介
代理模式的核心是代理类,给某一个真实对象提供一个代理对象,并由代理对象来控制对真实对象的访问。
代理模式是一种结构型设计模式。
代理模式分为两类:静态代理、动态代理。
简单来说,代理模式就是创建一个代理对象来代替真实对象做事。
所谓代理,就是代替的意思,代替雇主做事。
比如明星和经纪人的关系,明星就是真实对象,经纪人就是代理对象。
 
静态代理
调用代理对象(ProxyObject)重写接口的方法时,实际上执行的是目标对象(TargetObject)的方法。
模式中存在三种角色:
Subject:抽象主题角色,可以是抽象类(Abstract)也可以是接口(interface),这是一个最普通的业务类型定义,其中定义的方法交给实现子类和代理类实现。
RealSubject:真实主题角色,就是真实对象(TargetObject),被代理的对象,是业务真实执行者。
Proxy:代理主题角色,也叫委托类、代理对象(ProxyObject)。用来代替真实对象的访问。
目标对象是实现一个接口的目标对象。
优点:可以做到在不修改目标对象的功能前提下,对目标功能扩展
缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护
针对以上缺陷,可以使用动态代理方式

 

以购物为例,在执行购物方法前后都打印一句话。
复制代码
interface ShoppingService {
        void go();
    }

    public static class ShoppingServiceImpl implements ShoppingService {

        @Override
        public void go () {
            System.out.println("正在购物呢");
        }
    }

    public static class ShoppingServiceProxy implements ShoppingService {

        private final ShoppingService service;

        public ShoppingServiceProxy (ShoppingService service) {
            this.service = service;
        }

        @Override
        public void go () {
            System.out.println("准备去购物啦");
            service.go();
            System.out.println("购物完回家咯");
        }
    }

    public static void main (String[] args) {

        // 真实对象
        ShoppingService service = new ShoppingServiceImpl();

        // 代理对象
        ShoppingServiceProxy serviceProxy = new ShoppingServiceProxy(service);
        serviceProxy.go();
    }
复制代码

 

动态代理
动态代理有两种:JDK 动态代理、cglib 动态代理。
动态代理是通过字节码操作框架,在 JVM 运行时,通过目标接口或对象,计算出代理类的字节码,
然后加载到 JVM 中使用,其调用目标对象的方法也是通过 Java 反射实现。
对比一下,静态代理不会自动生成额外的代理对象,所有的对象都是用户在显式操作,性能上与普通模式没有区别。
而动态代理需要自动生成额外的字节码代理对象,调用也是通过 Java 反射,性能上略有影响。
 
JDK 动态代理
目标对象是实现一个接口的实现类对象,代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理。
其实际是调用代理对象(ProxyObject)的方法时,使用反射调用目标对象(TargetObject)的方法。
代理对象与静态代理一样都是代理接口的实现类,只不过是由 JVM 运行时动态创建的。
InvocationHandler 和 Proxy 是 JDK 动态代理最核心的部分,
Proxy 提供了创建动态代理类和实例的静态方法,InvocationHandler 是由代理实例的调用处理程序(即真实对象)实现的接口。
每个代理实例都有一个关联的调用处理程序(被代理的真实对象)。
当在代理实例上调用方法时,方法调用被编码并分派到其调用处理程序的 invoke方法(使用反射调用真实对象的方法)。

 

 
以恰饭为例,恰饭前后打印一句话。
复制代码
interface Service {
    void help();
}

public static class ServiceImpl implements Service {

    @Override
    public void help () {
        System.out.println("正在恰饭");
    }
}

public static class DynamicProxy implements InvocationHandler {

    // 真实代理对象 -> Service 实现类
    private final Object service;

    // DynamicProxy 代理 Service 实现类
    public DynamicProxy (Object service) {
        this.service = service;
    }

    @Override
    public Object invoke (Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("马上要恰饭咯");
        method.invoke(service, args);
        System.out.println("恰完饭了呢");
        return null;
    }
}

public static void main (String[] args) {
    Service service = new ServiceImpl();

    // 写入真实代理对象
    InvocationHandler handler = new DynamicProxy(service);

    // 创建代理对象
    // Classloader -> 指定当前目标对象使用类加载器,获取加载器的方法是固定的
    // Interface -> 目标对象实现的接口的类型,使用泛型方式确认类型
    // InvocationHandler -> 事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入
    Service serviceProxy = (Service) Proxy.newProxyInstance(service.getClass().getClassLoader(), service.getClass().getInterfaces(), handler);

    // 调用代理对象方法
    serviceProxy.help();
}
复制代码

 

CGLib 代理
也叫作子类代理(FastClass 的子类并不是委托类的子类),动态生成子类方式是,
解析委托类字节码,
使用字节码处理框架 ASM 向生成类中写入委托类实例直接调用方法的语句,
并为委托类通过方法签名和参数类型建立索引,
调用时通过索引调用委托类方法。
使用这种模板方式可以解决 Java 语法不支持的问题,同时改善 Java 反射性能。
相比其他代理方式,cglib 可以代理普通的类,无论目标类是否实现接口。
需要注意的是,委托类和委托类方法不能是 final 类型。
Enhancer 和 MethodInterceptor 是整个代理过程的核心。
Enhancer 用于生成基于委托类字节码的 FastClass 子类,
MethodInterceptor 拦截对于委托类的调用,将调用方法签名解析为索引,再通过索引调用生成类中对于委托类方法的调用。

 相关演示代码如下

复制代码
/**
 * <p style="color:rgb(0,255,0);">委托类或者说被代理类</p>
 **/
public static class Meal {
    public void eat() {
        System.out.println("干饭人淦饭");
    }
}

/**
 * <p style="color:rgb(0,255,0);">方法拦截器</p>
 * 用户调用代理类的方法将在此被拦截,实现对目标方法的调用、参数修改以及功能增强
 **/
public static class MealInterceptor implements MethodInterceptor {

    /** 
     * <p style="color:rgb(255,165,0);">方法描述</p> 
     * @param o 委托对象
     * @param method 委托方法或者说拦截方法
     * @param objects 方法参数
     * @param methodProxy 可用于调用原始方法,或在相同类型的不同对象上调用相同方法。
     * @return 返回代理方法的返回值
     */ 
    @Override
    public Object intercept (Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        // 调用前增强
        System.out.println("马上要干饭咯");

        // 调用指定对象的原始(超级)方法。
        methodProxy.invokeSuper(o, objects);

        // 调用后增强
        System.out.println("干饭完了呢");
        return null;
    }
}

public static void main (String[] args) {

    // Enhancer -> 生成动态子类以启用方法拦截, 动态生成的子类覆盖超类的非最终方法,并具有回调到用户定义的拦截器实现的钩子。
    Enhancer enhancer = new Enhancer();

    // 设置超类,或者说父类
    enhancer.setSuperclass(Meal.class);

    // 设置回调方法拦截器(MethodInterceptor),这里使用数组可设置多个拦截器
    enhancer.setCallback(new MealInterceptor());

    // 创建代理类并强制转换为超类
    Meal meal = (Meal) enhancer.create();

    // 调用代理类方法
    meal.eat();
}
复制代码

 


 
反编译代码
通过 JDK 工具可以捕获程序运行时,CGLib 生成的 FastClass 子类
 
复制代码
/**
  Enhancer 基于委托类(Meal)生成的 FastClass 子类
 */
public class CglibDynamicProxy$Meal$$FastClassByCGLIB$$94a6679 extends FastClass {

    // 超类(父类) FastClass 中的 invoke 方法,调用委托类(Meal)的委托方法(eat)
    // var1 -> 通过方法签名 + 参数类型列表计算出委托方法的索引
    // var2 -> 委托类实例
    // var3 -> 参数列表
    public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        Meal var10000 = (Meal)var2;
        int var10001 = var1;

        try {
            switch(var10001) {
            case 0:
                // 委托方法
                var10000.eat();
                return null;
            case 1:
                return new Boolean(var10000.equals(var3[0]));
            case 2:
                return var10000.toString();
            case 3:
                return new Integer(var10000.hashCode());
            }
        } catch (Throwable var4) {
            throw new InvocationTargetException(var4);
        }

        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }

    public CglibDynamicProxy$Meal$$FastClassByCGLIB$$94a6679(Class var1) {
        super(var1);
    }
    
    /**
      根据构造方法索引调用委托类构造方法
      var1 -> 方法索引
      var3 -> 参数列表
     */
    public Object newInstance(int var1, Object[] var2) throws InvocationTargetException {
        Meal var10000 = new Meal;
        Meal var10001 = var10000;
        int var10002 = var1;

        try {
            switch(var10002) {
            case 0:
                var10001.<init>();
                return var10000;
            }
        } catch (Throwable var3) {
            throw new InvocationTargetException(var3);
        }

        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }

    // 根据方法参数类型列表查询匹配构造函数的索引
    public int getIndex(Class[] var1) {
        switch(var1.length) {
        case 0:
            return 0;
        default:
            return -1;
        }
    }

    // 通过方法签名 + 参数类型列表返回匹配方法的索引
    public int getIndex(String var1, Class[] var2) {
        switch(var1.hashCode()) {
        case -1776922004:
            if (var1.equals("toString")) {
                switch(var2.length) {
                case 0:
                    return 2;
                }
            }
            break;
        case -1295482945:
            if (var1.equals("equals")) {
                switch(var2.length) {
                case 1:
                    if (var2[0].getName().equals("java.lang.Object")) {
                        return 1;
                    }
                }
            }
            break;
        case 100184:
            if (var1.equals("eat")) {
                switch(var2.length) {
                case 0:
                    return 0;
                }
            }
            break;
        case 147696667:
            if (var1.equals("hashCode")) {
                switch(var2.length) {
                case 0:
                    return 3;
                }
            }
        }

        return -1;
    }

    // 通过方法签名的包装类找到委托类方法索引
    // Signature -> 方法签名的表示形式,包含方法名、返回类型和参数类型。
    public int getIndex(Signature var1) {
        String var10000 = var1.toString();
        switch(var10000.hashCode()) {
        case -1310345955:
            if (var10000.equals("eat()V")) {
                return 0;
            }
            break;
        case 1826985398:
            if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
                return 1;
            }
            break;
        case 1913648695:
            if (var10000.equals("toString()Ljava/lang/String;")) {
                return 2;
            }
            break;
        case 1984935277:
            if (var10000.equals("hashCode()I")) {
                return 3;
            }
        }

        return -1;
    }

    // 返回这个类的最大方法索引
    public int getMaxIndex() {
        return 3;
    }
}
复制代码

 


 
相关链接
spring-core cglib 包的部分源码下载不到,可通过以下链接查看源码:
posted @   维维尼~  阅读(240)  评论(0编辑  收藏  举报
编辑推荐:
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示