设计模式之代理模式详解和应用

Posted 赵广陆

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式之代理模式详解和应用相关的知识,希望对你有一定的参考价值。

目录


1 代理模式定义

都知道 SpringAOP 是用代理模式实现,到底是怎么实现的?我们来一探究竟,并且自己仿真手写 还原部分细节。

代理模式(ProxyPattern)是指为其他对象提供一种代理,以控制对这个对象的访问,属于结构型模式。

在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标 对象之间起到中介的作用。

官方原文:Provide a surrogate or placeholder for another object to control access to it.

首先来看代理模式的通用UML类图:

代理模式一般包含三种角色:

抽象主题角色(Subject):抽象主题类的主要职责是声明真实主题与代理的共同接口方法,该类可以是接口也可以是抽象类;

真实主题角色(RealSubject):该类也被称为被代理类,该类定义了代理所表示的真实对象,是负责执行系统真正的逻辑业务对象;

代理主题角色(Proxy):也被称为代理类,其内部持有 RealSubject 的引用,因此具备完全的对 RealSubject的代理权。客户端调用代理对象的方法,同时也调用被代理对象的方法,但是会在代理对 象前后增加一些处理代码。

在代码中,一般代理会被理解为代码增强,实际上就是在原代码逻辑前后增加一些代码逻辑,而使调用者无感知代理模式属于结构型模式,分为静态代理动态代理

2 代理模式的应用场景

生活中的租房中介、售票黄牛、婚介、经纪人、快递、事务代理、非侵入式日志监听等,都是代理 模式的实际体现。当无法或不想直接引用某个对象或访问某个对象存在困难时,可以通过也给代理对象 来间接访问。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。

3 代理模式的通用写法

下面是代理模式的通用代码展示。

首先创建代理主题角色ISubject类:

public interface ISubject 
    void request();

创建真实主题角色RealSubject类:

public class RealSubject implements ISubject 
    public void request() 
        System.out.println("real service is called.");
    

创建代理主题角色Proxy类:

public class Proxy implements ISubject 

    private ISubject subject;

    public Proxy(ISubject subject)
        this.subject = subject;
    

    public void request() 
        before();
        subject.request();
        after();
    

    public void before()
        System.out.println("called before request().");
    

    public void after()
        System.out.println("called after request().");
    

客户端调用代码:

public class Client 
    public static void main(String[] args) 
        Proxy proxy = new Proxy(new RealSubject());
        proxy.request();
    

运行结果

called before request().
real service is called.
called after request().

4 从静态代理到动态代理

举个例子,有些人到了适婚年龄,其父母总是迫不及待地希望早点抱孙子。而现在在各种压力之下, 很多人都选择晚婚晚育。于是着急的父母就开始到处为自己的子女相亲,比子女自己还着急。下面来看代码实现。

静态代理:

创建顶层接口IPerson的代码如下:

public interface IPerson 
    void findLove();

儿子张三要找对象,实现ZhangSan类:

public class ZhangSan implements IPerson 
    public void findLove() 
        System.out.println("儿子要求:肤白貌美大长腿");
    

父亲张老三要帮儿子张三相亲,实现Father类:

public class ZhangLaosan implements IPerson 

    private ZhangSan zhangsan;

    public ZhangLaosan(ZhangSan zhangsan) 
        this.zhangsan = zhangsan;
    

    public void findLove() 
        // before
        System.out.println("张老三开始物色");
        zhangsan.findLove();
        // after
        System.out.println("开始交往");
    

来看测试代码:

public class Test 
    public static void main(String[] args) 
        ZhangLaosan zhangLaosan = new ZhangLaosan(new ZhangSan());
        zhangLaosan.findLove();
    

运行结果:

张老三开始物色
儿子要求:肤白貌美大长腿
开始交往

但上面的场景有个弊端,就是自己父亲只会给自己的子女去物色对象,别人家的孩子是不会管的。

但社会上这项业务发展成了一个产业,出现了媒婆、婚介所等,还有各种各样的定制套餐。如果还使用静态代理成本就太高了,需要一个更加通用的解决方案,满足任何单身人士找对象的需求。

这就是由静态代理升级到了动态代理。

采用动态代理基本上只要是人(IPerson)就可以提供相亲服务。

动态代理的底层实现一般不用我们自己亲自去实现,已经有很多现成的API。

在Java生态中,目前最普遍使用的是JDK自带的代理和Cglib提供的类库

下面我们首先基于JDK的动态代理支持如来升级一下代码。

首先,创建媒婆(婚介所)类JdkMeipo:

public class JdkMeipo implements InvocationHandler 
    
    private IPerson target;
    
    // 反射获取
    public IPerson getInstance(IPerson target)
        this.target = target;
        Class<?> clazz =  target.getClass();
        return (IPerson) Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
        before();
        Object result = method.invoke(this.target,args);
        after();
        return result;
    

    private void after() 
        System.out.println("双方同意,开始交往");
    

    private void before() 
        System.out.println("我是媒婆,已经收集到你的需求,开始物色");
    

再创建一个类ZhaoLiu:

public class ZhaoLiu implements IPerson 

    public void findLove() 
        System.out.println("赵六要求:有车有房学历高");
    

    public void buyInsure() 
    

测试代码如下:

public class Test 
    public static void main(String[] args) 
        JdkMeipo jdkMeipo = new JdkMeipo();
        IPerson zhangsan = jdkMeipo.getInstance(new Zhangsan());
        zhangsan.findLove();
        
        IPerson zhaoliu = jdkMeipo.getInstance(new ZhaoLiu());
        zhaoliu.findLove();
    

运行效果如下:

我是媒婆,已经收集到你的需求,开始物色
张三要求:肤白貌美大长腿
双方同意,开始交往
我是媒婆,已经收集到你的需求,开始物色
赵六要求:有车有房学历高
双方同意,开始交往

5 静态模式在业务中的应用

这里“小伙伴们”可能会觉得还是不知道如何将代理模式应用到业务场景中,我们来看一个实际的业务场景。

在分布式业务场景中,通常会对数据库进行分库分表,分库分表之后使用 Java操作时就可能需要配置多个数据源,我们通过设置数据源路由来动态切换数据源

先创建Order订单类:

@Data
public class Order 
    private Object orderInfo;
    //订单创建时间进行按年分库
    private Long createTime;
    private String id;

复制

创建OrderDao持久层操作类:

public class OrderDao 
    public int insert(Order order)
        System.out.println("OrderDao创建Order成功!");
        return 1;
    

创建IOrderService接口:

public interface IOrderService 
    int createOrder(Order order);

创建OrderService实现类:

public class OrderService implements IOrderService 
    private OrderDao orderDao;

    public OrderService()
        //如果使用Spring应该是自动注入的
        //我们为了使用方便,在构造方法中将orderDao直接初始化了
        orderDao = new OrderDao();
    

    public int createOrder(Order order) 
        System.out.println("OrderService调用orderDao创建订单");
        return orderDao.insert(order);
    

接下来使用静态代理,主要完成的功能是:根据订单创建时间自动按年进行分库

根据开闭原则,我们修改原来写好的代码逻辑,通过代理对象来完成。

先创建数据源路由对象,使用ThreadLocal的单例实现DynamicDataSourceEntity类:

public class DynamicDataSourceEntity 

    public final static String DEFAULE_SOURCE = null;

    private final static ThreadLocal<String> local = new ThreadLocal<String>();

    private DynamicDataSourceEntity()

    public static String get()return  local.get();

    public static void restore()
         local.set(DEFAULE_SOURCE);
    

    //DB_2018
    //DB_2019
    public static void set(String source)local.set(source);

    public static void set(int year)local.set("DB_" + year);

创建切换数据源的代理类OrderServiceSaticProxy:

public class OrderServiceStaticProxy implements IOrderService 
    private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");

    private IOrderService orderService;
    public OrderServiceStaticProxy(IOrderService orderService) 
        this.orderService = orderService;
    

    public int createOrder(Order order) 
        before();
        Long time = order.getCreateTime();
        Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
        System.out.println("静态代理类自动分配到【DB_" +  dbRouter + "】数据源处理数据" );
        DynamicDataSourceEntity.set(dbRouter);
        this.orderService.createOrder(order);
        DynamicDataSourceEntity.restore();
        after();
        return 0;
    

    private void before() System.out.println("Proxy before method."); 
    private void after() System.out.println("Proxy after method."); 

来看测试代码:

public class DbRouteProxyTest 
    public static void main(String[] args) 
        try 
            Order order = new Order();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
            Date date = sdf.parse("2020/03/01");
            order.setCreateTime(date.getTime());
            IOrderService orderService = new OrderServiceStaticProxy(new OrderService());
            orderService.createOrder(order);
         catch (Exception e) 
            e.printStackTrace();
        
    

运行结果如下:

Proxy before method.
静态代理类自动分配到【DB_2020】数据源处理数据
OrderService调用orderDao创建订单
OrderDao创建Order成功!
Proxy after method.

结果符合我们的预期。现在再来回顾一下类图,看是不是和我们最先画的一致,如下图所示。

动态代理和静态代理的基本思路是一致的,只不过动态代理功能更加强大,随着业务的扩展适应性更强。

6 动态代理在业务中的应用

上面的案例理解了,我们再来看数据源动态路由业务,帮助“小伙伴们”加深对动态代理的印象。

创建动态代理的类OrderServiceDynamicProxy:

package com.oldluedu.vip.pattern.proxy.dbroute.proxy;

import com.oldluedu.vip.pattern.proxy.dbroute.db.DynamicDataSourceEntity;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.SimpleDateFormat;
import java.util.Date;

public class OrderServiceDynamicProxy implements InvocationHandler 
    private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
    private Object target;

    public Object getInstance(Object target) 
        this.target = target;
        Class<?> clazz = target.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
        before(args[0]);
        Object object = method.invoke(target, args);
        after();
        return object;
    

    private void before(Object target) 
        try 
            System.out.println("Proxy before method.");
            Long time = (Long) target.getClass().getMethod("getCreateTime").invoke(target);
            Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
            System.out.println("动态代理类自动分配到【DB_" + dbRouter + "】数据源处理数据");
            DynamicDataSourceEntity.set(dbRouter);
         catch (Exception e) 
            e.printStackTrace();
        
    

    private void after() 
        System.out.println("Proxy after method.");
    

测试代码如下:

public class DbRouteProxyTest 
    public static void main(String[] args) 
        try 
            Order order = new Order();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
            Date date = sdf.parse("2020/03/01");
            order.setCreateTime(date.getTime());
            IOrderService orderService = (IOrderService) new OrderServiceDynamicProxy().getInstance(new OrderService());
            orderService.createOrder(order);
         catch (Exception e) 
            e.printStackTrace();
        
    

运行效果如下:

Proxy before method.
静态代理类自动分配到【DB_2020】数据源处理数据
OrderService调用orderDao创建订单
OrderDao创建Order成功!
Proxy after method.

依然能够达到相同运行效果。但是,使用动态代理实现之后,我们不仅能实现 Order的数据源动态 路由,还可以实现其他任何类的数据源路由。当然,有个比较重要的约定,必须实现getCreateTime() 方法,因为路由规则是根据时间来运算的。我们可以通过接口规范来达到约束的目的,在此就不再举例。

7 手写JDK动态代理实现原理

不仅知其然,还得知其所以然。既然JDK动态代理功能如此强大,那么它是如何实现的呢?我们现 在来探究一下原理,并模仿JDK动态代理动手写一个属于自己的动态代理。

我们都知道JDK动态代理采用字节重组,重新生成对象来替代原始对象,以达到动态代理的目的。

7.1 JDK动态代理的实现原理

  1. 获取被代理对象的引用,并且获取它的所有接口(反射获取)。
  2. JDK Proxy类重新生成一个新的类,实现了被代理类所有接口的方法。
  3. 动态生成Java代码,把增强逻辑加入到新生成代码中。
  4. 编译生成新的Java代码的class文件。
  5. 加载并重新运行新的class,得到类就是全新类。

7.2 CGLib动态代理容易踩的坑

  1. 无法代理final修饰的方法。

以上过程就叫字节码重组。JDK中有一个规范,在ClassPath下只要是$开头的.class文件,一般都是自动生成的。那么我们有没有办法看到代替后的对象的“真容”呢?做一个这样测试,我们将内存中 的对象字节码通过文件流输出到一个新的.class文件,然后利用反编译工具查看其源代码。

public class Test 
    public static void main(String[] args) 
        JdkMeipo jdkMeipo = new JdkMeipo();
        IPerson zhangsan = jdkMeipo.getInstance(new Zhangsan());
        zhangsan.findLove();
        // 通过反编译工具可以查看源代码
        try 
            byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]IPerson.class);
            FileOutputStream os = new FileOutputStream("F://$Proxy0.class");
            os.write(bytes);
            os.close();
         catch (IOException e) 
            e以上是关于设计模式之代理模式详解和应用的主要内容,如果未能解决你的问题,请参考以下文章

设计模式之代理模式详解(java)

设计模式之代理模式(Proxy)详解及代码示例

设计模式——代理模式

Java动态代理 深度详解

代理设计模式之静态代理与动态代理(超..)详解

代理模式详解