Java设计模式之代理模式

1. 代理模式概述
  • 什么是代理模式?
  1. 通过代理对象访问目标对象,这样可以在不修改目前对象的前提下,提供额外的功能操作,达到扩展目标对象功能的目的。
  2. 代理模式是设置一个中间代理来控制对目标对象的访问,以增强目标对象功能和简化访问的方式
  3. 比如,明星的本职工作是演出,这时需要一个经纪人为他接洽演出活动。经纪人就是明星的代理,联系经纪人就可以邀请明星进行表演。
  • 代理模式的三种类型: 静态代理、动态代理(JDK动态代理或接口代理)、CGLIB动态代理。
  • 代理模式有三种角色:
  1. 抽象对象(AbstractObject): 声明了目标对象和代理对象的共同接口,这样在任何可以使用目标对象的地方都可以使用代理对象。
  2. 目标对象(RealObject): 定义了代理对象所代理的对象。
  3. 代理对象(ProxyObject): 代理对象内部含有目标对象的引用,可以在任何时候操作目标对象;代理对象提供与目标对象相同的接口,可以在任何时候替代目标对象。代理对象通常在客户端调用传递给目标对象之前或之后,执行某些额外的操作,而不是单纯将调用传递给目标对象。
    在这里插入图片描述
    代理模式的类图
2. 静态代理
① 静态代理的概述及编程实例
  • 静态类提前定义好了代理类和目标类的关系(二者实现同一个接口),编译完成后代理类是一个实际的class文件。如,腾讯的代理律师是老张,一旦腾讯遇到法律纠纷,都由老张负责处理这些纠纷。
  • 静态代理建立的步骤:
  1. 定义一个接口作为抽象角色
  2. 创建目标类,实现接口作为目标角色
  3. 创建代理类,实现与目标类相同的接口作为代理角色
  • 静态代理的编程实例1 :
  1. 考虑为每个同学记录1000米跑的时间,同学应该只负责跑步,而老师去进行时间记录。这时,同学就是目标对象,老师就是代理对象。
  2. 以下代码通过聚合实现了静态类,目标对象是代理对象的内部对象。
import java.util.Random;

interface Run {
    void run();
}

class Student implements Run {
    private String name;

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

    @Override
    public void run() {
        System.out.println(this.name + "正在跑1000米...");
        // 模拟跑步,让线程随机休眠一段时间。指定时间范围,防止长时间休眠
        try {
            Thread.sleep(new Random().nextInt(3000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class Teacher implements Run {
    // 目标对象
    private Student target;

    public Teacher(Student target) {
        this.target = target;
    }

    @Override
    public void run() {
        System.out.println("开始1000米计时...");
        long start = System.currentTimeMillis();
        target.run();
        long end = System.currentTimeMillis();
        System.out.println("停止计时,一共跑了" + (end - start) + "秒");
    }
}

public class StaticProxy {
    public static void main(String[] args){
        // 创建目标对象
        Student targetObj=new Student("张三");
        // 目标对象作为代理对象的内部对象
        Teacher proxyObj=new Teacher(targetObj);
        // 访问代理对象的run方法,该方法对目标对象的run方法进行了扩展
        proxyObj.run();
    }
}
  • 通过聚合实现静态代理,运行结果如下:
    在这里插入图片描述
  • 静态代理的编程实例2:
  1. 通过继承实现静态代理:代理类继承目标类,重写目标类中的方法。
  2. 继承的方式并不灵活,一般使用聚合方式实现静态代理。
import java.util.Random;

interface Run {
    void run();
}

class Student implements Run {
    private String name;

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

    @Override
    public void run() {
        System.out.println(this.name + "正在跑1000米...");
        // 模拟跑步,让线程随机休眠一段时间。指定时间范围,防止长时间休眠
        try {
            Thread.sleep(new Random().nextInt(3000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class Teacher extends Student {
   public Teacher(String name){
       super(name);
   }

    @Override
    public void run() {
        System.out.println("开始1000米计时...");
        long start = System.currentTimeMillis();
        super.run();
        long end = System.currentTimeMillis();
        System.out.println("停止计时,一共跑了" + (end - start) + "秒");
    }
}

public class StaticProxy {
    public static void main(String[] args){
        Teacher proxyObj=new Teacher("李四");
        proxyObj.run();
    }
}
  • 通过继承实现静态代理,运行结果如下:
    在这里插入图片描述
② 静态代理的优缺点
  • 静态代理总结:
  1. 静态代理要求代理类和目标类实现同一个接口,编译完成后代理类是一个实际的class文件。
  2. 优点: 可以在不修改目标对象的前提下,通过代理对象扩展目标对象的功能。
  3. 缺点1: 由于要求代理类和目标类实现相同的接口,使得目标类和代理类是一对一的,会产生过多的目标类并导致代码冗余
  4. 缺点2: 一旦接口增加方法,代理类和目标类都要进行修改,使得代码不易维护
  5. 静态代理的缺点,可以使用动态代理来解决。
3. JDK动态代理
① 动态代理的重要接口和类
  • 动态代理:代理类不需要实现与目标类实现相同的接口,使用JDK API,利用反射机制,在运行时动态生成代理类的字节码并加载到JVM中。
  • 动态代理由java.lang.reflect包实现,主要使用了其中的Proxy类InvocationHandler接口
  1. java.lang.reflect.InvocationHandler接口:调用处理器接口,自定义了一个invoke()方法,用于集中处理在Proxy类对象上的方法调用,在该方法中实现了对目标对象的代理访问
  2. java.lang.reflect.Proxy类:提供了一组静态方法,最常使用的是newProxyInstance()静态方法,返回一个Proxy类对象。
  • newProxyInstance()方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
  1. ClassLoader loader: 指定目标对象使用的类加载器,获取类加载器的方法是固定的。
  2. Class<?>[] interfaces: 指定目标对象使用的接口类型
  3. InvocationHandler h: 指定调用处理器,执行目标对象的方法时,会触发调用处理器的invoke()方法,并把目标对象的方法作为参数传入。
  • newProxyInstance()的常见使用方法:
  1. 如何获取ClassLoader
// 方法一:目标类.class.getClassLoader()
Mouse.class.getClassLoader()
// 方法二:目标对象.getClass().getClassLoader()
mouse.getClass().getClassLoader()
  1. 如何获取目标对象的接口?
// 方法一:通过new Class[]{接口1.class, ... , 接口n.class}获取
new Class[]{Mouse.class}
// 方法二:通过目标对象.getClass().getInterfaces()获取
mouse.getClass().getInterfaces()
  1. 如何指定调用处理器?
// 预先创建InvocationHandler的子类对象,传入目标对象
Mouse mouse = new Mouse("Tom");
MyInvocationHandler handler = new MyInvocationHandler(mouse);
Animal iAnimal = (Animal) Proxy.newProxyInstance(Mouse.class.getClassLoader(),
                new Class[] {Mouse.class}, handler);
  • InvocationHandlerinvoke()方法:
  1. Object proxy: 代理对象
  2. Method method: 被代理对象调用的目标对象上的方法
  3. Object[] args: 方法的调用参数
Object invoke(Object proxy, Method method, Object[] args)
② 动态代理编程实例
  • 动态代理建立的步骤:
  1. 定义抽象对象接口及实现接口的目标类
  2. 自定义实现了InvocationHandler接口的调用处理器类,重写其中的invoke(代理对象,被调用的方法,方法的调用参数)方法。
  3. 通过Proxy类的静态方法newProxyInstance(目标对象的类加载器,目标对象的接口,调用处理器)方法生成代理对象
  4. 通过代理对象调用方法,实现对目标对象中方法的代理。
  • 编程实例: 使用动态代理,同时为不同的对象进行计时代理。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Random;

interface EatLunch {
    void eat();
}

class Customer implements EatLunch {
    private String name;

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

    @Override
    public void eat() {
        System.out.println(name + "正在全聚德吃烤鸭...");
        try {
            Thread.sleep(new Random().nextInt(200));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class TimerInvocationHandler implements InvocationHandler {
    private Object target;

    public TimerInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("开始计时!");
        long start = System.currentTimeMillis();
        Object result = method.invoke(target, args);
        long end = System.currentTimeMillis();
        System.out.println("计时结束,一共花费了" + (end - start) + "分钟!");
        return result;
    }
}

public class DynamicProxy {
    public static void main(String[] args) {
        // 定义目标对象,使用代理对象实现孩子跑步计时
        Runnable child = new Child("星星");
        // 定义调用处理器对象,传入目标对象
        TimerInvocationHandler handler = new TimerInvocationHandler(child);
        // 使用定义代理对象
        Runnable runProxy = (Runnable) Proxy.newProxyInstance(child.getClass().getClassLoader(),
                child.getClass().getInterfaces(), handler);
        // 调用跑步方法,动态实现代理
        runProxy.run();

        // 定义目标对象,实现对顾客吃饭计时
        Customer customer = new Customer("李四");
        handler = new TimerInvocationHandler(customer);
        EatLunch eatProxy = (EatLunch) Proxy.newProxyInstance(Customer.class.getClassLoader(),
                new Class[]{EatLunch.class}, handler);
        eatProxy.eat();
    }
}

interface Runnable {
    void run();
}

class Child implements Runnable {
    private String name;

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

    @Override
    public void run() {
        System.out.println(name + "正在操场上跑步...");
        try {
            Thread.sleep(new Random().nextInt(10));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  • 动态代理运行结果截图:
    在这里插入图片描述
③ 动态代理与静态代理的区别
  • 动态代理与静态代理的比较:
  1. 编译时与运行时: 静代理在编译时实现,编译完成后代理类是一个实际的class文件;而动态代理在运行时实现,编译完成代理类没有实际的class文件,在运行时动态生成代理类的字节码并加载到JVM中。
  2. 接口的实现: 静态代理要求代理类实现与目标类相同的接口,动态代理则不需要,可以解决静态代理中代理类太多、代码冗余且不易维护的缺点。
4. CGLIB动态代理
① CGLIB动态代理概述
  • 使用JDK API实现动态代理,要求目标类必须实现接口,若目标类不存在接口,则无法使用该方式。这时,可以使用CGLIB(Code Generation Library)实现动态代理。
  • CGLIB是一个强大的、高性能的代码生成类库,底层使用小而快的字节码处理框架ASM来转换字节码生成新的子类。子类中使用方法拦截技术拦截父类调用的方法,从而实现对父类方法的动态代理。
  • 在CGLIB动态代理中,看出子类是增强过的,子类对象就是代理对象。
  • 注意:
  1. 目标类不能是final类型。如过目标类是final类型,则无法创建其子类,会报错。
  2. 目标对象的方法不能是final和static类型。 如果为final或static类型,则无法被拦截,也就无法执行额外的功能。
  • Spring AOPdynaop都使用到了CGLIB实现动态代理,JDK代理与CGLib动态代理均是实现Spring AOP的基础
② CGLIB动态代理编程实例
  • CGLIB建立动态代理的步骤:
  1. 创建目标类
  2. 创建实现MethodInterceptor接口的代理工厂类:
    ① 目标对象作为其内置对象;
    ② 定义一个方法:该方法利用工具类Enhancer创建目标对象的子类对象(代理对象);
    ③ 重写intercept()方法实现具体的代理业务。
  3. 动态代理的使用:
    ① 创建目标对象
    ② 创建代理对象,需要将目标对象作为参数传入
    ③ 通过代理对象调用目标对象的方法,实现动态代理。
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

// 创建目标类,不需要实现接口
class Star {
    private String name;

    public Star(){}

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

    public void sing() {
        System.out.println(name + "唱了《忘情水》");
    }
}

// 创建代理工厂类,需要实现
class ProxyFactory implements MethodInterceptor {
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    // 给目标对象创建代理对象
    public Object getProxyInstance() {
        // 工具类
        Enhancer enhancer = new Enhancer();
        // 设置父类
        enhancer.setSuperclass(target.getClass());
        // 设置回调函数
        enhancer.setCallback(this);
        // 创建子类对象,即创建代理对象
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("表演即将开始...");
        Object result=method.invoke(target,objects);
        System.out.println("表演结束,谢谢观看!");
        return result;
    }
}

public class CGLIBProxy {
    public static void main(String[] args) {
        // 创建目标对象
        Star star=new Star("刘德华");
        // 创建代理对象
        Star startProxy=(Star) new ProxyFactory(star).getProxyInstance();
        // 代理对象调用目标对象的方法,实现动态代理
        startProxy.sing();
    }
}
  • CGLIB动态代理程序运行结果截图:
    在这里插入图片描述
  • 注意:
  1. 需要同时添加cglib.jarasm.jar,如果只添加了cglib.jar报错如下:
Exception in thread "main" java.lang.NoClassDefFoundError: org/objectweb/asm/Type
...
// 在以下代码处处报错
Star startProxy=(Star) new ProxyFactory(star).getProxyInstance();
  1. 如果目标对象定义了有参构造函数,必须显示定义无参构造函数。否则,报错如下:
Exception in thread "main" java.lang.IllegalArgumentException: Superclass has 
	no null constructors but no arguments were given
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值