Proxy 代理模式 静态代理 动态代理 JDK cglib 结构型

本文详细介绍了代理模式的概念及其在Java中的应用,包括静态代理与动态代理的区别、实现方式及各自的优缺点。
Markdown版本笔记我的GitHub首页我的博客我的微信我的邮箱
MyAndroidBlogsbaiqiantaobaiqiantaobqt20094baiqiantao@sina.com

Proxy 代理模式 静态代理 动态代理 JDK cglib 结构型


目录

代理模式简介

proxy [ˈprɒksi] n.代理服务器;代表权;代理人,代替物;委托书;
invoke [ɪnˈvəʊk] vt.乞灵,祈求;召鬼;借助; n. invocation
subject [ˈsʌbdʒɪkt] n.主题,话题;学科,科目;v. 提供,提出;使…隶属;

代理模式是一种结构型设计模式,其目的就是为对象提供一个代理以控制对其访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

  • 代理模式是用一个简单的对象来代替一个复杂的对象
  • 代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的访问
  • 代理模式主要使用了 Java 的多态,干活的是被代理类,代理类主要是接活。

为什么不直接访问原对象而要加个代理呢?

  • 采用代理模式可以有效的将具体的实现调用方进行解耦,通过面向接口进行编程完全将具体的实现隐藏在内部
  • 更通俗的说,代理解决的问题是:当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类即可;而且代理的出现还可以让我们完成与另一个类之间的关系的统一管理

代理模式按照代理类的创建时期,可以分为两种:

  • 静态代理:代理类在程序运行前就已经被开发者在Java代码中写好了的。
  • 动态代理:代理类并不是在Java代码中定义的,而是在程序运行时运用反射机制动态创建而成。

静态代理

静态代理简介

静态代理的优点:

  • 客户端不需要知道委托类是什么、怎么做的,只需知道代理类即可,也即可以隐藏委托类的实现细节
  • 可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理

静态代理的缺点:

  • 代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法,但是因为代理类中接口的方法往往是没什么逻辑的,它通常只是调用了委托类的同名方法而已,所以这就导致出现了大量重复、冗余的代码
  • 如果接口中增加或修改了某个方法,除了所有委托类需要修改代码外,所有代理类也需要修改代码,增加了代码维护的复杂度
  • 代理对象只服务于一种类型的对象,即一个静态代理类只能为特定的某一个接口服务,如想要为多个接口服务则需要建立多个代理类,这在大型系统中大大增加了复杂度。

静态代理的一般实现过程:

  • 首先定义一个接口
  • 然后创建实现这个接口的具体实现类,具体实现类中需要将接口中定义的方法的业务逻辑功能实现
  • 再创建一个代理类同样实现这个接口,代理类中接口的方法只要调用具体类中的对应方法即可

静态代理案例

首先定义一个接口

interface IUser {
 void eat(String s);
 void run();
}

然后创建实现这个接口的具体实现类

class UserImpl implements IUser {
    private String name;

    public UserImpl(String name) {
        this.name = name;
    }

    @Override
    public void eat(String s) {
        System.out.println(name + "吃" + s);
        //省去代码三千行
    }

    @Override
    public void run() {
        System.out.println(name + "开始跑");
        //省去代码三千行
    }
}

再创建一个代理类同样实现这个接口

class UserProxy implements IUser { //核心1:代理类和被代理类要实现相同的接口
    private IUser user; //核心2:要在代理类中指定具体的被代理人

    public UserProxy() {
        this.user = new UserImpl("包青天"); //客户端可以不指定具体的被代理类对象
    }

    public UserProxy(IUser user) {
        this.user = user; //客户端也可以指定具体的被代理类对象,当然也可以通过set方式指定被代理类对象
    }

    @Override
    public void eat(String s) {
        System.out.println("eat前的操作");
        user.eat(s);//核心3:在调用代理类的方法时,实际是调用的被代理类的方法,利用的就是多态的特性
        System.out.println("eat后前的操作");
    }

    @Override
    public void run() {
        user.run();
    }
}

使用案例

new UserProxy().eat("苹果"); //不指定委托类的对象
new UserProxy(new UserImpl("baiqiantao")).run(); //指定委托类的对象

动态代理

静态代理是在编译时就将接口、实现类、代理类一股脑儿全部手动完成。但如果我们需要很多的代理,每一个都这么手动的去创建实属浪费时间,而且会有大量的重复代码,且难以扩展,此时我们就可以采用动态代理。

动态代理可以在程序运行时根据需要动态的创建代理类及其实例来完成具体的功能。主要用的是 JAVA 的反射机制。动态代理中的代理类并不是在 Java 代码中定义的,而是在运行时根据我们在 Java 代码中的"指示"动态生成的。

JDK 动态代理案例

首先定义一个或多个接口:

interface IUser {
    void eat(String s);
    void run();
}
interface IWork {
    String work(String s);
}

然后创建实现这个(些)接口的具体实现类:

class UserImpl implements IUser, IWork {
    private String name;

    public UserImpl(String name) {
        this.name = name;
    }

    @Override
    public void eat(String s) {
        System.out.println(name + "吃" + s);
        //省去代码三千行
    }

    @Override
    public void run() {
        System.out.println(name + "开始跑");
        //省去代码三千行
    }

    @Override
    public String work(String s) {
        System.out.println(name + "工作:" + s);
        //省去代码三千行
        return "走上人生巅峰";
    }
}

然后创建一个 InvocationHandler ,目的是获取代理对象:

注意,和静态代理不同的是,我们创建的这个InvocationHandler可以代理任何的委托类,而不必为每一个接口或抽象类创建单独的代理类,除非我们需要在 invoke 方法中针对不同的委托类做不同额外的处理。

class MyInvocationHandler implements InvocationHandler {
    private Object target;//持有委托类的引用,但并不限定委托类必须是某一接口或某一抽象类

    public MyInvocationHandler(Object target) {//这里是用Object接收的,所以可以传递任何类型的对象,而不局限与IUser或IWork
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("------------------------before------------------------"); //在目标对象的方法执行之前可以执行一些逻辑
        Object returnObj = method.invoke(target, args); //【核心点】通过反射执行某个类的某方法
        System.out.println("------------------------after------------------------"); //在目标对象的方法执行之后也可以执行一些逻辑
        System.out.println("【代理类】" + proxy.getClass().getName() //【$Proxy0】
                + "\n【代理类的超类】" + proxy.getClass().getSuperclass().getName() //【java.lang.reflect.Proxy】
                + "\n【代理的方法声明】" + method.toGenericString() //【public abstract void IUser.eat(java.lang.String)】
                + "\n【传入方法的实参】" + Arrays.toString(args) //【苹果】
                + "\n【方法执行后的返回值】" + returnObj); //【null】
        return returnObj;
    }

    //获取目标对象的代理对象
    public Object getProxy() {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), //也可以使用【this.getClass().getClassLoader()】
                target.getClass().getInterfaces(), this);
    }

    //为什么要提供这么一个方法?等会看一下下面的解释就明白了!
    public Object getProxy(Class<?>[] interfaces) {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), interfaces, this);
    }
}

然后我们就可以愉快的玩耍了,比如这样:

IUser user = new UserImpl("包青天");
IUser proxy = (IUser) new MyInvocationHandler(user).getProxy();
proxy.eat("苹果");

或者这样:

IUser user = new UserImpl("包青天");
IWork proxy = (IWork) new MyInvocationHandler(user).getProxy();
proxy.work("Android开发");

甚至这样:

CheckBox checkBox = findViewById(R.id.check_box);
Checkable proxy = (Checkable) new MyInvocationHandler(checkBox).getProxy(new Class[]{Checkable.class});
checkBox.setOnCheckedChangeListener((view, isCheck) -> proxy.setChecked(isCheck)); //代理了CheckBox的选择状态改变事件

JDK 动态代理的局限性

我们先不从原理层面去分析这个问题,我们先从 Proxy 这个类的 newProxyInstance 方法的设计上去分析。

newProxyInstance 方法的参数列表如下:

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

这里面要求提供三个参数,第一个是 ClassLoader,ClassLoader这个东西我还不是特别懂,但是你只需要知道系统提供了很多种类型的ClassLoader,你还可以自定义ClassLoader。比如你如果要加载网络上的一个class文件,在这样的情况下,默认的ClassLoader就不能满足我们的需求了,你就需要定义自己的ClassLoader。所以,为了应对各种业务场景,这里让你提供一个ClassLoader是很可以理解的。

然后再说一个第二个参数interfaces,几乎所有的代码都是通过这样来获取接口列表的:

target.getClass().getInterfaces()

你有没有想过,如果这样就可以了的话,为何不让你直接提供一个委托类的对象target,甚至为何不直接去掉这个参数呢?你看我们在构建最后那个那个参数InvocationHandler时已经传进去了一个委托类的对象target,那我们就完全没有必要再单独把target传进去了呀!

要解释这个问题,我们从下面一段代码说起:

interface I1 {}
interface I2 {}
class A implements I1 {}
class B extends A implements I2 {}

那么请问,通过下面的代码获取到的都有哪些接口呢?

System.out.println(Arrays.toString(new B().getClass().getInterfaces()));

答案是只有 I2 而没有其父类实现的接口 I1。
如果你觉得是我用错方法了,你觉得API中应该有另一个方法可以获取类 B 实现的接口以及类 B 的父类所实现的接口,那么我很遗憾的告诉你,没有你要的这个API!

当然我们可以通过类似如下方式自己遍历所有父类实现的接口:

private static Class[] getInterfaces(Object object) {
    List<Class> list = new ArrayList<Class>();
    Class clazz = object.getClass();
    do {
        list.addAll(Arrays.asList(clazz.getInterfaces()));
        clazz = clazz.getSuperclass();
    } while (clazz != Object.class);

    return list.toArray(new Class[list.size()]);
}

但是总觉得不是很舒服,因为这完全是API应该设计好的,为何让我们自己费这么大劲去遍历呢!如果你知道不提供这个API的原因,希望你可以告诉我。

我们继续回到刚才那个问题,(可能是)因为API没有提供方法让我们获取一个类所实现的接口以及其父类所实现的接口的集合,所以为了将动态代理设计的更简单以及更通用,系统干脆让我们自己去定义这个接口集合好了。

说了这么多,其实还没说到局限性到底局限到哪呢!其实看过上面的废话你也已经明白了,局限就局限在这个接口集合上,如果一个类没有实现接口,或者一个类中的某个方法不是重写的接口中的方法,这时我们就没办法提供接口集合,所以我们也就没办法使用动态代理了!

实际上,JDK的动态代理是非常有局限性的,特别是在Android这种大量基于事件的系统中!

Cglib动态代理案例

cglib是一个功能强大,高性能的代码生成包。

cglib的原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,由于是通过创建子类来代理父类,因此不能代理被final修饰的类(代理final修饰的类会抛异常,代理final修饰的方法只会原样执行委托类的方法而不能做任何拦截)。

但是cglib有一个很致命的缺点:cglib的底层是采用著名的ASM字节码生成框架,使用字节码技术生成代理类,也就是通过操作字节码来生成的新的.class文件,而我们在android中加载的是优化后的.dex文件,也就是说我们需要可以动态生成.dex文件代理类,因此cglib在Android中是不能使用的。

要使用cglib,需要添加两个jar包:
CGLib 2.2
ASM 3.3.1

委托类:

class UserImpl {
    private String name;

    public UserImpl() {
    }

    public UserImpl(String name) {
        this.name = name;
    }

    public String work(String s) {
        System.out.println(name + "工作:" + s);
        //省去代码三千行
        return "走上人生巅峰";
    }

    public final void finalWork(String s) {
        System.out.println("==========================" + name + "工作:" + s);
    }
}

Cglib代理类实现方式一

这种方式类似于JDK动态代理的方式,都是直接调用委托类对象的方法:

class CglibProxy implements MethodInterceptor {
    private Object target;

    public Object getProxy(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer(); //Cglib 核心类,生成代理对象
        enhancer.setSuperclass(target.getClass());//设置代理对象的父类
        enhancer.setCallback(this);//设置增强功能的类对象,拦截器
        return enhancer.create();//获取委托类的代理对象,调用这个方法要求委托类必须有【无参构造方法】,否则必须带参数的 create 方法
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("------------------------before------------------------"); //在目标对象的方法执行之前可以执行一些逻辑
        Object returnObj = proxy.invoke(target, args); //和JDK动态代理完全一样,通过反射调用委托类对象的方法
        System.out.println("------------------------after------------------------"); //在目标对象的方法执行之后也可以执行一些逻辑
        System.out.println("【代理类】" + obj.getClass().getName() //【UserImpl$$EnhancerByCGLIB$$22569189】
                + "\n【代理类的超类】" + obj.getClass().getSuperclass().getName() //【UserImpl】
                + "\n【代理的方法声明】" + method.toGenericString() //【public abstract void IUser.eat(java.lang.String)】
                + "\n【传入方法的实参】" + Arrays.toString(args) //【Android开发】
                + "\n【方法执行后的返回值】" + returnObj); //【走上人生巅峰】
        return returnObj;
    }
}

使用案例:

UserImpl proxy = (UserImpl) new CglibProxy().getProxy(new UserImpl("包青天"));
proxy.work("JavaEE开发");
proxy.finalWork("Android开发");

Cglib代理类实现方式二

这种方式不需要提供委托类的对象,

class CglibProxy implements MethodInterceptor {

    public Object getProxy(Class clazz) {
        Enhancer enhancer = new Enhancer(); //Cglib 核心类,生成代理对象
        enhancer.setSuperclass(clazz);//设置代理对象的父类
        enhancer.setCallback(this);//设置增强功能的类对象,拦截器
        return enhancer.create();//获取委托类的代理对象,调用这个方法要求委托类必须有无参构造方法
    }

    public Object getProxy(Class clazz, Class[] args, Object[] argsValue) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create(args, argsValue);//【new Class[] { String.class }】【 new Object[] { "包青天" }】
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("------------------------before------------------------"); //在目标对象的方法执行之前可以执行一些逻辑
        Object returnObj = proxy.invokeSuper(obj, args); //通过反射执行某个类的某方法,注意不能调用【invoke】方法
        System.out.println("------------------------after------------------------"); //在目标对象的方法执行之后也可以执行一些逻辑
        System.out.println("【代理类】" + obj.getClass().getName() //【UserImpl$$EnhancerByCGLIB$$22569189】
                + "\n【代理类的超类】" + obj.getClass().getSuperclass().getName() //【UserImpl】
                + "\n【代理的方法声明】" + method.toGenericString() //【public abstract void IUser.eat(java.lang.String)】
                + "\n【传入方法的实参】" + Arrays.toString(args) //【Android开发】
                + "\n【方法执行后的返回值】" + returnObj); //【走上人生巅峰】
        return returnObj;
    }
}

使用案例:

UserImpl proxy = (UserImpl) new CglibProxy().getProxy(UserImpl.class);
proxy.work("JavaEE开发");
UserImpl proxy2 = (UserImpl) new CglibProxy().getProxy(UserImpl.class, new Class[] { String.class }, new Object[] { "包青天" });
proxy2.finalWork("Android开发");

2018-10-5

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值