Java代理模式

1. 代理模式


​ 代理模式就是通过代理对象来代替真实对象的访问。通过代理,就能实现在不变动原目标对象的前提下,提供额外的功能操作,拓展目标对象的一些功能。这便是它的主要作用。

​ 例如:服务员代替厨师处理客人的点菜,厨师收到的信息都是服务员处理过后的菜单。服务员此时就可以被视为厨师的代理对象,代理的方法是处理客人点菜。了解设计模式

​ 代理模式有两种实现方式,分别是: 静态代理、动态代理。

2. 静态代理


2.1 介绍

​ 在静态代理中,目标对象的方法增强均是通过手动修改具体代码完成,非常不灵活(每一次修改,都要修改代码)。从JVM层面看,静态代理在编译阶段就把相关文件(接口、实现类、代理类)变为一个个Class文件。

2.2 实现步骤

​ 实现步骤:

  1. 定义一个接口及其实现类;
  2. 创建一个代理类同样实现这个接口。
  3. 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。

2.3 代码示例

​ 1. 定义发送短信的接口

public interface SmsService {
    String send(String message);
}

​ 2. 实现发送短信的接口

public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("发送了一条消息:" + message);
        return message;
    }
}

​ 3. 创建代理类并同样实现发送短信的接口

public class SmsProxy implements SmsService {
    private SmsService smsService;

    public SmsProxy(SmsService smsService) {
        this.smsService = smsService;
    }

    public String send(String message) {
        // 调用前操作
        System.out.println("我提前看到了消息:" + message);
        smsService.send(message);
        // 调用后操作
        System.out.println("我收到了消息:" + message);
        return null;
    }
}

​ 4. 实际使用

public class Main {
    public static void main(String[] args) {
        SmsService smsService = new SmsServiceImpl();
        SmsProxy smsProxy = new SmsProxy(smsService);
        smsProxy.send("hello world!");
    }
}

​ 控制台输出:

我提前看到了消息:hello world!
发送了一条消息:hello world!
我收到了消息:hello world!

3. 动态代理


​ 相较于静态代理,动态代理更为灵活。不需要针对每个目标类都单独创建一个代理类,也不需要必须实现接口,可以直接代理实现类(CGLIB动态代理机制)

​ 从JVM角度来看,动态代理是在程序运行时动态生成类字节码,并加载到JVM中。

​ 动态代理在日常开发中的使用频率相对较少,但在框架中是几乎必用的一门技术。

​ 在Java中动态代理类的实现方式有多种,比如 JDK动态代理、CGLIB动态代理等。

3.1 JDK动态代理机制


3.1.1 介绍

JDK动态代理存在问题:只能代理实现了接口的类。

​ 在Java 动态代理机制中InvocationHandler接口和Proxy类是核心。

Proxy类方法如下图所示,其中使用频率最高的方法是:newProxyInstance(),这个方法主要用于生成一个代理对象。

在这里插入图片描述

    /**
     * 返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序
     * 可以传递给代理的参数有几个限制。
     * * 接口数组中的所有Class对象必须表示接口,而不是类或基本类型。
     * * interface数组中的两个元素不能引用相同的Class对象。
     * * 所有接口类型必须通过指定的类装入器按名称可见。换句话说,对于类装入器cl和每个接口i,以下表达式必须为真: 	Class. forName(i. getName(), false, cl) == i
     * * 所有非公共接口必须在同一个包中;否则,代理类就不可能实现所有的接口,而不管它在哪个包中定义。
     * * 对于具有相同签名的指定接口的任何成员方法集:
     * * * 如果任何方法的返回类型是基本类型或void,则所有方法必须具有相同的返回类型。
     * * * 否则,其中一个方法必须具有可赋值给其他方法的所有返回类型的返回类型。
     * * 生成的代理类不能超过虚拟机对类施加的任何限制。例如,虚拟机可以限制一个类可以实现的接口数量为65535;此时,接口阵列的大小不能超过65535。
     * 如果违反了任何这些限制,将抛出一个IllegalArgumentException。如果接口数组参数或其任何元素为空,则会抛出NullPointerException。	
     * 请注意,指定代理接口的顺序很重要:对具有相同接口组合但顺序不同的代理类的两个请求将导致两个不同的代理类。
     * @param   loader:类加载器,用于加载代理对象。
     * @param   interfaces:代理类要实现的接口列表
     * @param   h:将方法调用分派到的调用处理程序,也就是实现了InvocationHandler接口的对象。
     **/
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
       ......
    }

​ 要实现动态代理类的话,还必须要实现InvocationHandler来自定义处理逻辑。当代理对象调用一个方法时,方法的调用就会被转发到InvocationHandler接口实现类的invoke方法来调用。

public interface InvocationHandler {

    /**
     * 处理代理实例上的方法调用并返回结果。当在于其关联的代理实例上调用方法时,将在调用处理程序上调用此方法
     * @param   proxy:方法被调用的代理实例。
     * @param   method:对应于在代理实例上调用的接口方法的method实例。Method对象的声明类将是在其中声明方法的接口,该接口可能是代理类继承该方法所通过的代理接口的超接口。
     * @param   args:包含方法中传递的参数值的对象数组。在代理实例上调用,如果接口方法不接受参数,则为空,基本类型的参数被包装在适当的基本包装器的实例中类,如 java.lang.Integer或 java.lang.Boolean
     */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

​ 换而言之:通过Proxy类的newProxyI你Stance()创建的代理对象在调用方法的时候,实际上会调用到InvocationHandler 接口的实现类的invoke()方法。我们可以在invoke()方法中自定义处理逻辑。

3.1.2 JDK动态代理类使用步骤

  1. 定义一个接口及实现类。
  2. 实现InvocationHandler接口并重写invoke方法,在invoke方法中会调用原生方法(被代理对象的方法)并自定义一些处理逻辑。
  3. 通过 Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法创建代理对象。

3.1.3 代码示例

​ 1. 定义发送短信的接口

public interface SmsService {
    String send(String message);
}

​ 2. 实现发送短信的接口

public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("发送了一条消息:" + message);
        return message;
    }
}
  1. 实现InvocationHandler接口
public class DebugInvocationHandler implements InvocationHandler {
    private final Object target;

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

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 调用前操作
        System.out.println("我提前看到了消息:" + args[0].toString());
        Object result = method.invoke(target, args);
        // 调用后操作
        System.out.println("我收到了消息:" + args[0].toString());
        return result;
    }
}
  1. 定义代理对象工厂
public class JdkProxyFactory {
    public static Object getProxy(Object target){
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new DebugInvocationHandler(target)
        );
    }
}
  1. 实际使用
public class Main {
    public static void main(String[] args) {
        SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
        smsService.send("hello world!");
    }
}

​ 控制台输出:

我提前看到了消息:hello world!
发送了一条消息:hello world!
我收到了消息:hello world!

3.2 CGLIB动态代理机制


3.2.1 介绍

​ 为了解决JDK动态代理的问题(只能代理实现了接口的类),可以通过使用CGLIB动态代理机制来避免。

CGLIB是一个基于ASM的字节码生成库,它允许我们在运行时对字节码文件进行修改和动态生成。CGLIB通过继承方法实现代理。在Spring中的AOP模块中,如果目标对象实现了接口, 则默认采用JDK动态代理,否则采用CGLIB动态代理。

​ 在CGLIB动态代理机制中MethodInterceptor接口和Enhancer类是核心。

​ 我们需要实现MethodInterceptoer并重写intercept方法,intercept方法用于拦截增强被代理类。

public interface MethodInterceptor extends Callback {
    /**
     * @param   var1:被代理的对象
     * @param   var2: 被拦截的方法
     * @param   args:包含方法中传递的参数值的对象数组。在代理实例上调用,如果接口方法不接受参数,则为空,基本类型的参数被包装在适当的基本包装器的实例中类,如 java.lang.Integer或 java.lang.Boolean
     * @param   var4:用于原生方法调用
     */ 
    Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}

​ 我们可以通过Enhancer类进行动态获取被代理类,当代理类调用方法时,实际调用的是MethodInterceptor中的intercept方法。

3.2.2 CGLIB 动态代理类使用步骤

  1. 定义一个类。
  2. 实现MethodInterceptor并重写intercept方法。
  3. 通过Enhancer类的create()创建代理类。

3.2.3 代码示例

​ 因为CGLIB属于开源项目,使用它时,需要手动添加依赖。

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>
1.  定义一个发送短信的类。
public class SmsService {
    public String send(String message){
        System.out.println("发送了一条短信:" + message);
        return message;
    }
}
2. 实现 `MethodInterceptor`
public class DebugMethodInterceptor implements MethodInterceptor {
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 调用前操作
        System.out.println("我提前看到了消息:" + objects[0].toString());
        Object result = methodProxy.invokeSuper(o, objects);
        // 调用后操作
        System.out.println("我收到了消息:" + objects[0].toString());
        return result;
    }
}
3. 获取代理对象
public class CglibProxyFactory {
    public static Object getProxy(Class<?> clazz){
        // 创建动态代理类
        Enhancer enhancer = new Enhancer();
        // 设置类加载器
        enhancer.setClassLoader(clazz.getClassLoader());
        // 设置被代理类
        enhancer.setSuperclass(clazz);
        // 设置方法的拦截类
        enhancer.setCallback(new DebugMethodInterceptor());
        // 创建代理类
        return enhancer.create();
    }
}
4.  实际使用
public class Main {
    public static void main(String[] args) {
        SmsService smsService = (SmsService) CglibProxyFactory.getProxy(SmsService.class);
        smsService.send("hello world!");
    }
}

​ 控制台输出:

我提前看到了消息:hello world!
发送了一条短信:hello world!
我收到了消息:hello world!

4. 静态代理与动态代理区别


  1. 灵活性:动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,不需要对每个代理目标都创建一个代理类。动态代理在接口新增时,无需进行修改。
  2. JVM层面:静态代理在编译阶段就将代码文件变成一个个实际class文件,而动态代理是在运行时动态生成类字节码,并加载到JVM中。

5. 仓库地址

文章代码仓库

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值