19.代理模式

本章目标

  1. 简介
  2. 静态代理
  3. 动态代理

本章内容

一、简介

1、什么是代理

代理这个词相信大家并不陌生,我们大家都知道代理商,简单地说就是代替厂家卖商品,厂家委托代理为其销售商品,顾客找代理购买商品。

通过使用代理,通常有两个优点,并且能够分别与我们提到的代理商的几个特点对应起来:

  1. 关于代理商,首先我们从他们那里买东西时通常不知道背后的厂家究竟是谁,也就是说,委托者对我们来说是不可见的;
  2. 代理可以对顾客进行定位,更精确的售卖给需要的客户群体

2、代理模式

代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。

代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。

代理模式:为其他对象提供一种代理以控制对这个对象的访问,也就是创建一个代理对象作为客户端和目标对象之间的中介,主要目的就是保护目标对象或增强目标对象

通过使用代理模式,通常有以下两个优点:

  1. 可以隐藏被代理类的实现
  2. 可以实现客户与被代理类间的解耦,在不修改被代理类代码的情况下能够做一些额外的处理

3、分类

Proxy( /ˈprɑːksi/)模式在实际中经常应用,比如Windows系统提供的快捷方式

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

  • 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了
  • 动态代理:在程序运行时,运用反射机制动态创建而成。

4、Proxy模式结构图

代理模式结构图如下:

由三部分组成:

  • Proxy:保存一个引用使得代理可以访问实体。控制对实体的存取,并可能负责创建和删除它,其他功能依赖于代理的类型。
  • Subject:定义RealSubject和Proxy的共用接口,这样就在任何使用RealSubject的地方都可以使用Proxy。
  • RealSubject:定义Proxy所代表的实体。

二、静态代理

若代理类在程序运行前就已经存在,那么这种代理方式被成为 静态代理 ,这种情况下的代理类通常都是我们在Java代码中定义的

所谓的静态代理,就是通过声明一个明确的代理类来访问源对象,一个代理只能服务于一种产品,当有n种产品时,就需要n个代理,这样就不利于业务的发展。

举例:我们有两个接口,Computer和 Phone,每个接口都有一个实现类

1、定义接口(Subject)

代理接口

public interface  Computer{

    public void sell();

}

2、定义接口实现类(RealSubject)

即委托类

public class HuaWeiComputer implements Computer{

    @Override
    public void sell() {
        System.out.println("出售华为电脑");
    }

}

3、定义代理类(Proxy)

代理类,最终还是调用委托类实现业务操作

现在我们要做的就是让代理在调用sell()前输出一句售前了解,调用后输出一句售后服务

public class ComputerProxy implements Computer {
    private Computer computer;

    public ComputerProxy(Computer computer) {
        this.computer = computer;
    }

    @Override
    public void sell() {
        System.out.println("售前了解");
        computer.sell();
        System.out.println("售后服务");
    }
}

4、测试

public class Test {

    public static void main(String[] args) {

        Computer huaWeiComputer = new HuaWeiComputer();
        ComputerProxy computerProxy = new ComputerProxy(huaWeiComputer);
        computerProxy.sell();

    }

}
--------------------------
输出:
售前了解
出售华为电脑
售后服务

5、优缺点

Phone和computer一样再实现一遍

静态代理的代码非常简单易懂,这种模式虽好,但是也有明显的缺点:

  • 会存在大量冗余的代理类,这里只有两个接口,如果有n个接口,那么就要定义n个代理类。
  • 不易维护,一旦接口更改,代理类和被代理类都要更改。

那么这个时候就可以使用动态代理来解决了

三、动态代理

代理类在程序运行时创建的代理方式被成为 动态代理。 也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的指示动态生成的 ,在程序运行时,运用反射机制动态创建而成代理对象。

代理类在程序运行时创建代理的方式叫动态代理,也就是说代理类并不是在java代码中定义的,而是在运行的时候动态生成的。

1、实现方式

Java的动态代理技术的实现主要有两种方式:

  • JDK原生动态代理:是Java原生支持的,不需要外部依赖,但是它只能基于接口进行代理(需要动态代理的对象必须实现与某个接口)
  • CGLIB动态代理:CGLIB通过继承的方式进行代理,(让需要代理的类成为Enhancer的父类),无论目标对象有没有实现接口都可以代理,但是无法处理Final的情况。

2、JDK动态代理

JDK从1.3版本就开始支持动态代理类的创建。

java.lang.reflect类库中提供三个类直接支持代理模式:ProxyInvocationHandlerMethod

2.1、Proxy类

Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。

核心方法newProxyInstance:返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。

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

参数说明:

loader - 定义代理类的类加载器 interfaces - 代理类要实现的接口列表 h - 指派方法调用的调用处理程序

Ps:类加载器 在Proxy类中的newProxyInstance()方法中需要一个ClassLoader类的实例,ClassLoader实际上对应的是类加载器

2.2、InvocationHandler接口

代理实例的调用处理程序 实现的接口

每个代码实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。

package java.lang.reflect;
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

参数说明:

Object proxy:在其上调用方法的代理实例。

Method method:要调用的方法

Object[] args:方法调用时所需要的参数

可以将InvocationHandler接口的子类想象成一个代理的最终操作类,替换掉ProxySubject。

2.3、Method

Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息。所反映的方法可能是类方法或实例方法(包括抽象方法)。

主要方法invoke:对带有指定参数的指定对象调用由此 Method 对象表示的基础方法

public Object invoke(Object obj,
                     Object... args)

参数: obj - 从中调用基础方法的对象 args - 用于方法调用的参数

2.4、定义代理类处理程序

public class JDKProxy {

    public static Object getProxy(Object target) {
        // 返回代理接口的代理对象,并指定代理对象的调用处理程序为当前对象
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    //proxy - 在其上调用方法的代理实例
                    //method - 代表被调用的方法
                    //args - 调用方法的参数
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("售前了解");
                        // 执行方法,底层还是通过委托类对象调用自己的方法执行
                        Object invoke = method.invoke(target, args);
                        System.out.println("售后服务");
                        return invoke;
                    }
                });
    }
}

当我们通过代理对象调用业务方法时,这个调用会转送到invoke方法中,代理类对象作为proxy参数传入,参数method标识了我们具体调用的是代理类的哪个方法,args为这个方法的参数。

这样一来,我们对代理类中的所有方法的调用都会变为对invoke的调用,这样我们可以在invoke方法中添加统一的处理逻辑(也可以根据method参数对不同的代理类方法做不同的处理)。

因此我们可以在中介类的invoke方法中实现输出售前了解,再调用被代理类的方法,再输出售后服务。

2.5、执行代码

public class App{
    public static void main( String[] args ){
        //得到委托类
        HuaWeiComputer huaWeiComputer = new HuaWeiComputer();
        //得到代理类
        Computer proxy = (Computer)JDKProxy.getProxy(huaWeiComputer);
        //通过代理对象调用方法
        proxy.sell();
    }
}

输出:

售前了解
出售华为电脑
售后服务

可以看到无论多少个接口,只需要一个代理类就可以了。

3、CGLIB动态代理

CGLIB通过继承的方式进行代理,(让需要代理的类成为Enhancer(增强剂)的父类),无论目标对象有没有实现接口都可以代理,但是无法处理Final的情况。

3.1、引入依赖

导入cglib-3.1.jarasm-4.2.jar

3.2、代理类

package com.woniuxy.proxy;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CGLIBProxy {

    public static Object getProxy(Object target) {
        Enhancer enhancer = new Enhancer();
        //设置需要创建子类的类
        enhancer.setSuperclass(target.getClass());
        //设置回调程序,调用时会执行该程序
        enhancer.setCallback(new MethodInterceptor() {

            @Override
            // o: cglib 动态生成的代理类的实例
            // method:实体类所调用的都被代理的方法的引用
            // objects 参数列表
            // methodProxy:生成的代理类对方法的代理引用
            public Object intercept(Object arg0, Method method, Object[] arg2, MethodProxy arg3) throws Throwable {
                System.out.println("售前咨询");
                Object invoke = method.invoke(target, arg2);
                System.out.println("售后服务");
                return invoke;
            }
        });
        //创建代理对象
        return enhancer.create();
    }

}

3.2、测试

public class App{
    public static void main( String[] args ){
        //得到委托类
        HuaWeiComputer huaWeiComputer = new HuaWeiComputer();
        //得到代理类
        Computer proxy = (Computer)CGLIBProxy.getProxy(huaWeiComputer);
        //通过代理对象调用方法
        proxy.sell();

    }
}

4、JDK代理与CGLIB代理的区别

JDK动态代理实现接口,CGLIB动态继承思想

JDK动态代理(目标对象存在接口时)执行效率高于CIGLIB

如果对象有接口实现,选择JDK代理,如果没有接口实现选择CGILB代理

思维导图

16.代理模式.png

posted @ 2025-04-09 14:22  icui4cu  阅读(10)  评论(0)    收藏  举报