Java代理模式

文章介绍了代理模式的基本概念,包括节省对象创建成本、权限检查和屏蔽复杂性等作用。接着详细讲解了静态代理的接口实现和继承实现,指出静态代理的限制。然后重点讨论了JavaSDK动态代理,通过InvocationHandler实现方法的增强,并解释了动态代理的内部工作原理。最后提到了CGLib动态代理,它允许为没有接口的类创建代理,以适应更多场景。
摘要由CSDN通过智能技术生成

代理模式是最常用的设计模式之一,也是学习的 Spring AOP 的理论基础。

1. 关于代理

代理」是一个比较通用的词,作为一个软件设计模式,它在《设计模式》一书中被提出。编程领域中的代理的基本概念和日常生活中的概念是类似的。

代理背后一般至少有一个实际对象,代理的外部功能和实际对象一般是一样的,用户与代理打交道,不直接接触实际对象。

虽然外部功能和实际对象一样,但代理有它存在的价值,比如:

  1. 节省成本比较高的实际对象的创建开销,按需延迟加载,创建代理时并不真正创建实际对象,而只是保存实际对象的地址,在需要时再加载或创建。

  1. 执行权限检查,代理检查权限后,再调用实际对象。

  1. 屏蔽网络差异和复杂性,代理在本地,而实际对象在其它服务器上,调用本地代理时,本地代理请求其他服务器。

2. 静态代理

静态代理的实现有 2 种方案:基于接口实现和基于继承实现。

2.1 静态代理的接口实现方案

  1. 接口和被代理类的实现

public interface Animal {
    void eat();
}

public class Cat implements Animal {
    public void eat() {
        System.out.println("猫吃鱼");
    }
}
  1. 代理类的实现

public class CatProxy implements Animal {

    private Cat target;

    public CatProxy() {
        this.target = new Cat();
    }

    @Override
    public void eat() {
        System.out.println("执行前调用");
        target.eat();
        System.out.println("执行后调用");
    }
}

从上述的 UML 图可以看到,JDK 动态代理有一个限制:它要求被代理对象必须有一个接口

在整个方案中,如果 Animal 接口并不存在,那么就无法使用这个方案就无从实现。

2.2 静态代理的继承实现方案

(1)被代理类的实现

public class Cat {

    public void eat() {
        System.out.println("猫吃鱼");
    }

}

(2)代理类的实现

public class CatProxy extends Cat {

    public CatProxy() {
    }

    @Override
    public void eat() {
        System.out.println("执行前调用");
        super.eat();
        System.out.println("执行后调用");
    }

}

代理的继承实现方案对 Cat 类有无接口不作要求。不过继承也带来一个代价,那就是如果 Cat 类中有 final 方法,那么 CatProxy 无法对 final 方法进行增强。

3. Java SDK 动态代理

JDK 动态代理方案是实现代理模式的二种方式之一。

3.1 动态代理的概念

动态代理是一种强大的功能,它可以在运行时动态创建一个类,实现一个或多个接口,可以在不修改原有类的基础上动态地为通过该类获取的对象添加方法、修改行为。

动态代理有 2 种实现方式:

  • 一种是 Java SDK 提供的;

  • 一种是第三方库 (如 cglib) 提供的。

在动态代理中,代理类是动态生成的。

3.2 Java SDK 动态代理

(1)接口和被代理类的实现

public interface Animal {
    void eat();
}

public class Cat implements Animal {
    public void eat() {
        System.out.println("猫吃鱼");
    }
}

这里和静态代理中的 Animal 接口以及 Cat 类是一样的。

(2)调用处理类

public class SimpleInvocationHandler implements InvocationHandler {

    private Object realObj;

    public SimpleInvocationHandler(Object realObj) {
        this.realObj = realObj;
    }

    @Override
    public Object invoke(Object proxy, 
                             Method method,
                             Object[] args) throws Throwable {
        System.out.println("entering " + method.getName());
        Object result = method.invoke(realObj, args);
        System.out.println("leaving " + method.getName());
        return result;
    }
}

在 SimpleInvocationHandler 的 invoke 实现中,我们调用了 method 的 invoke 方法,传递了实际对象 realObj 作为参数,达到了调用实际对象对应方法的目的。

需要注意的是, 不能将 proxy 作为参数传递给 method.invoke 方法 。比如:

Object result = method.invoke(proxy, args);

上面的语句会出现死循环,因为 proxy 表示当前代理对象,这又会调用到 SimpleInvocationHandler 的 invoke 方法。

(3)生成动态代理对象

Animal cat = new Cat();
Animal proxyCat = (Animal) Proxy.newProxyInstance(
            Animal.class.getClassLoader(), 
            new Class<?>[]{Animal.class},
            new SimpleInvocationHandler(cat)
);
proxyCat.eat();

代理对象的创建使用了 java.lang.reflect 包中的 Proxy 类的静态方法 newProxyInstance 来创建。这个方法的声明如下:

public static Object newProxyInstance(
        ClassLoader loader, 
        Class<?>[] interfaces, 
        InvocationHandler h)

它有 3 个参数,具体如下:

  1. loader : 表示类加载器。在例子中,使用的是和 Animal 一样的类加载器。

  1. interfaces : 表示代理类要实现的接口列表。它是一个数组,元素的类型只能是接口,不能是普通类。在例子中,只有一个 Animal 。

  1. h 的类型为 InvocationHandler :它是一个接口,也定义在 java.lang.reflect 包中,它只定义了一个 invoke 方法,对带接口所有方法的调用都会转给这个方法。

(4)基本原理解释

在 main 方法中加入如下一行代码:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

javac 在编译项目时,会将 JDK 生成的 Cat 的动态代理类导出到一个 $Proxy0 的类文件中,它的内容如下:

public final class $Proxy0 extends Proxy implements Animal {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    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 void eat() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    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 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"));
            m3 = Class.forName("com.woniu.example.Animal").getMethod("eat");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            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,它有一个构造方法,接受一个 InvocationHandler 类型的参数,保存为实例变量 h (h 定义在父类 Proxy 中) 。

$Proxy0 实现了接口 Animal ,对于每个方法,如 eat 方法,它调用 InvocationHandler 的 invoke 方法;对于 Object 中的方法,如 hashCode、equals 和 toString,$Proxy0 同样转给 InvocationHandler 。

可以看出,$Proxy0 类的主要与接口数组有关,给定这个接口数组,它动态创建了每个接口的实现代码,而具体的实现就是:转发给 InvocationHandler 。$Proxy0 对象与被代理对象的关系以及对它的调用由 InvocationHandler 的实现负责。

4. CGLib 动态代理

Java SDK 动态代理的局限在于:它只能为接口创建代理,返回的代理对象也只能转换到某个接口类型,如果一个类没有接口,或者希望代理非接口中定义的方法,那就没有办法了。

有一个第三方的类库:cglibhttps://github.com/cglib/cglib) ,可以做到这一点,Spring、Hibernate 等框架都在使用该类库。

4.1 接口和被代理类

public interface Animal {
    void eat();
}

public class Cat implements Animal {
    public void eat() {
        System.out.println("猫吃鱼");
    }
}
这里和静态代理中的 Animal 接口以及 Cat 类是一样的。

4.2 实现代理逻辑

class SimpleInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object object, 
                            Method method,
                            Object[] args, 
                            MethodProxy proxy) 
                            throws Throwable {
        System.out.println("before " + method.getName());
        Object result = proxy.invokeSuper(object, args);
        System.out.println("after" + method.getName());
        return result;
    }

}

在这里我们实现了 MethodInterceptor 接口,它与 Java SDK 的 InvocationHandler 有点类似,方法名称变成了 intercept ,多了一个 MethodProxy 类型的参数。

与 JDK 动态代理方案中的 InvocationHandler 不同,这里的 SimpleInterceptor 中没有被代理的对象,它通过 MethodProxy#invokeSuper 方法调用被代理类的方法。

4.3 获得代理对象,并验证

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Cat.class);
enhancer.setCallback(new SimpleInterceptor());

Cat cat = (Cat) enhancer.create();
cat.eat();

在上面的例子中:

  • Cat 是被代理的类,它可以没有接口。

  • getProxy 方法能够为一个类生成代理对象,这个代理对象可以安全地转换为被代理类型,它使用了 cglib 的 Enhancer 类。

  • Enhancer 类的 getSuperclass 方法用来设置被代理的类,setCallback 方法用来设置被代理类的方法 (必须是 public 且非 final) 被调用时执行的代理逻辑。

  • SimpleInterceptor 实现了 MethodInterceptor 接口,用来封装代理逻辑。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值