Java内功修炼系列:代理模式及动态代理

957 阅读8分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

一 代理模式

1.1 简介

Java的动态代理在实践中有着广泛的使用场景,比如最场景的Spring AOP、Java注解的获取、日志、用户鉴权等。

先看百度百科的定义:

代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

代理模式解释起来就是,设定指定的“代理人(Proxy代理类)”执行RealSubject类中的具体方法。我们对RealSubject类中的左右操作都通过“代理人(Proxy代理类)调用RealSubject类进行执行。

通过代理模式,我们可以做到两点:

  1. 隐藏代理类的具体实现(RealSubject类中才是具体实现)。

  2. 实现客户与代理类的解耦,可以在不改变代理类代码的情况下添加一些额外的功能(日志、权限)等.

1.2 代理模式角色定义

在上述的过程中在编程的过程中我们可以定义为三类对象:

  • Subject(抽象主题角色):一般为接口,定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法。比如:出售商品等。
  • RealSubject(真实主题角色):真正实现业务逻辑的类。比如实现了广告、出售等方法的厂家(Factoryer)。
  • Proxy(代理主题角色):用来代理和封装真实主题。比如,同样实现了广告、出售等方法的超时(Shop)。 以上三个角色对应的类图如下:

二 静态代理

2.1 介绍和实例

静态代理是指代理类在程序运行前就已经存在,这种情况下的代理类通常都是我们在Java代码中定义的。

静态代理例子:工厂主(Factoryer)需要执行的工厂(IFactory)的主要function就是卖工厂的货(sell方法),我们现在需要委托商店(shop)这个代理来代卖货物。顾客也就是client是通过商店shop这个代理买东西的。

IFactory接口定义如下:

/**
 * Subject 接口
 * IFactory 定义了要做的事情,就是卖东西,但是工厂是委托给商店代卖的
 * 委托类和代理类都实现了IFactory接口
   **/
   public interface IFactory { 
   /** 出售 */
   void sell();

}

Vendor类定义如下

/**
 * RealSubject类
 * 真实主题类,是客户端通过代理类间接调用的  真正实现具体需要实现方法的主体。
 * 工厂主,他要卖东西,他是Factory这个抽象主体的具体实现,他要卖东西
   **/
   public class Factoryer implements IFactory{

   @Override
   public void sell() {
       System.out.println("出售货物");
   }

}

Shop类定义如下:

/**
 * Proxy代理类
 * 超市,代理卖东西
 **/
public class Shop implements IFactory{
 
    private Factoryer factoryer;
 
    public Shop(Factoryer factoryer){
        this.factoryer = factoryer;
    }
 
    @Override
    public void sell() {
        System.out.println("Shop是商店代理,用于直接对接client顾客,执行sell方法");
        factoryer.sell();
    }
}

其中代理类Shop通过聚合的方式持有了被代理类Factoryer的引用,并在对应的方法中调用Factoryer对应的方法。

下面看看在客户端中如何使用代理类,即客人怎么通过这套代理体系买东西。

public class ClientBuy {
 
    public static void main(String[] args) {
 
        // 被代理类
        Factoryer factoryer = new Factoryer();
 
        // 创建供应商(也就是真正执行卖东西这个方法操作的factoryer)的代理类Shop
        IFactoryer ifactoryer = new Shop(factoryer);
 
        // 客人买东西时面向的是代理类Shop。
        ifactoryer.sell();
    }
}

在上述代码中,我们可以在Shop中修改或新增一些内容,而不影响被代理类Factoryer 。比如我们可以在Shop类中新增一些额外的处理,类似于筛选购买用户、记录日志等操作。

2.2 静态代理的缺点

静态代理实现简单且不侵入原代码,但当场景复杂时,静态代理会有以下缺点:

1、当需要代理多个类时,代理对象要实现与目标对象一致的接口。我们只有两个选择,1:只维护一个代理类来实现多个接口,但这样会导致代理类过于庞大;2:新建多个代理类,但这样会产生过多的代理类。

2、当接口(IFactoryer)需要增加、删除、修改方法时,目标对象(ClientBuy)与代理类(shop)都要同时修改,不易维护。

于是,动态代理便派上用场了。

三 动态代理

动态代理是指代理类在程序运行时进行创建的代理方式。这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据Java代码中的“指示”动态生成的。

相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。

3.1 基于JDK原生动态代理实现

实现动态代理通常有两种方式:JDK原生动态代理和CGLIB动态代理。这里,我们以JDK原生动态代理为例来进行讲解。

JDK动态代理主要涉及两个类:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。

InvocationHandler接口定义了如下方法:

/**
 * 调用处理程序
 */
public interface InvocationHandler { 
    Object invoke(Object proxy, Method method, Object[] args); 
}

顾名思义,实现了该接口的中介类用做“调用处理器”。当调用代理类对象的方法时,这个“调用”会转送到invoke方法中。动态代理中,Proxy 动态产生的代理会调用 InvocationHandler 实现类,所以 InvocationHandler 是实际执行者。

InvocationHandler方法的,第一个参数proxy参数为代理类对象,第二个参数为method,表示具体调用的是代理类的哪个方法,第三个参数args为为第二个参数该方法的参数。

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

下面以添加日志为例来演示一下动态代理。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;
 
public class LogHandler implements InvocationHandler {
    Object target;  // 被代理的对象,实际的方法执行者
 
    public LogHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);  // 调用 target 的 method 方法
        after();
        return result;  // 返回方法的执行结果
    }
    // 调用invoke方法之前执行
    private void before() {
        System.out.println("调用方法sell之前的日志处理");
    }
    // 调用invoke方法之后执行
    private void after() {
        System.out.println("调用方法sell之后的日志处理");
    }
}

客户端编写程序使用动态代理代码如下:

import java.lang.reflect.Proxy;
/**
 * 动态代理测试
 *
 * @author sec
 * @version 1.0
 * @date 2020/3/21 10:40 AM
 **/
public class DynamicClientBuy {
    public static void main(String[] args) {
 
        // 创建中介类实例
        LogHandler logHandler = new LogHandler(new Factoryer());
 
        // 获取代理类实例IFactory
        IFactory iFactory = (IFactory) (Proxy.newProxyInstance(
                                            IFactory.class.getClassLoader(), 
                                            new Class[]{IFactory.class}, 
                                            logHandler)
                                        );
 
        // 通过代理类对象调用代理类方法,实际上会转到invoke方法调用
        iFactory.sell();
    }
}

roxy.newProxyInstance的三个参数含义为:

  • loader 自然是类加载器
  • interfaces 代码要用来代理的接口
  • h 一个 InvocationHandler 对象

执行之后,打印日志如下:

调用方法sell之前的日志处理
Shop sell goods
调用方法sell之后的日志处理

经过上述验证,我们发现已经成功为我们的被代理类统一添加了执行方法之前和执行方法之后的日志。

四 小结

了解代理模式可以让我们的系统设计的更加具有可扩展性。而动态代理的应用就更广了,各类框架及业务场景都在使用。

Java动态代理的优势是实现无侵入式的代码扩展,也就是方法的增强:不改变这个方法的前提下去丰富它;你可以在不用修改源码的情况下,增强一些方法;在方法的前后你可以做你任何想做的事情(甚至不去执行这个方法就可以)。老功能用原来的方法不会有问题,新功能通过代理丰富了原来的方法也能被广泛试用。

动态代理类:在程序运行时,通过反射机制动态生成。 动态代理类通常代理接口下的所有类。 动态代理事先不知道要代理的是什么,只有在运行的时候才能确定。 动态代理的调用处理程序必须事先InvocationHandler接口,及使用Proxy类中的newProxyInstance方法动态的创建代理类。 Java动态代理只能代理接口,要代理类需要使用第三方的CGLIB等类库

掌握到这里其实已经差不多了,关于更多一些例子包括源码的一些分析,可以看我的参考文章。

参考:

www.choupangxia.com/2020/03/21/…

blog.csdn.net/lovejj1994/…

blog.csdn.net/yaomingyang…

blog.csdn.net/u011784767/…

blog.csdn.net/briblue/art… ———————————————— 版权声明:本文为CSDN博主「许进进」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/LucasXu01/a…