代理模式

2

代理简单介绍

生活中的代理处处可见:黄牛 【三个角色。我(真实对象)黄牛(代理对象)火车票(目的对象)】
代理模式出现的原因

生活中代理出现的原因其实也是代码中代理模式出现的原因,技术来源生活,😄

  • 真实对象无法直接访问目的对象
  • 真实对象不想直接访问目的对象
  • 真实对象访问目的对象存在困难
解决的问题是:在直接访问对象时带来的问题 ,原先直接访问的两个类通过代理对象不在直接访问,可以实现解耦。
代理模式对应的UML图

图片描述

代理模式的分类

按照代理对象的创建时机划分为两种:
1、在编译前已经创建好对象,编译之后运行前代理类的.Class文件就已经存在,这种的称为静态代理
2、程序在运行的时候动态的创建代理对象,这种的称为动态代理

静态代理

在代码中手动的创建代理类。模拟的代码如 IDEA展示

/**
 * 定义一个接口
 * 真实对象要干什么,代理对象通过接口可以知道
 */
public interface IUserDo {
    void takeTrain(Ticket ticket);
}

/**
 * @author pangjianfei
 * @Date 2019/5/22
 * @desc 真实对象
 */
public class RealUser implements IUserDo {

    public final String NAME = "真实回家人";

    @Override
    public void takeTrain(Ticket ticket) {
        System.out.println(NAME + " : 抢到票了,我要回家了,花了"+ ticket.proxyPrice.intValue() + "元,值了!");
    }
}

/**
 * @author pangjianfei
 * @Date 2019/5/22
 * @desc 代理角色,黄牛
 */
public class ProxyUser implements IUserDo{

    public final String NAME = "黄牛";

    /**代理的是哪一位*/
    RealUser realUser;

    public ProxyUser(RealUser realUser) {
        this.realUser = realUser;
    }

    @Override
    public void takeTrain(Ticket ticket) {
        Ticket realTicket = buyTicket(ticket);
        realUser.takeTrain(realTicket);
        System.out.println(NAME + " : 我挣了" +(realTicket.proxyPrice.intValue()-realTicket.realPrice.intValue()) + "元");
    }

    public Ticket buyTicket(Ticket ticket) {
        System.out.println(NAME + " : 去买从"+ticket.from+"到"+ticket.to+"的火车票了");
        Ticket backHomeTicket = new Ticket();
        backHomeTicket.setFrom(ticket.from);
        backHomeTicket.setTo(ticket.to);
        backHomeTicket.setTakeOffDate(ticket.takeOffDate);
        backHomeTicket.setRealPrice(new BigDecimal(100));
        backHomeTicket.setProxyPrice(new BigDecimal(120));
        System.out.println(NAME + " : 购买成功");
        return backHomeTicket;
    }
}
  • 静态代理可以在不修改真实角色的代码,通过添加代理类,对目标的功能进行扩展
  • 代理对象需要和目标真实对象实现相同的接口,所以会有很多的代理类。如果接口进行维护,那么需要修改很多类的代码。同时代码增加,类增加,也是提高了复杂度。

动态代理

在程序运行的时候,动态的创建需要的代理对象,原理与静态代理一样,只是没有具体的代理类,直接通过反射生成了一个代理对象。

在生成代理对象的时候,有几种实现方法:

基于JDK实现的动态代理

基于JDK的动态代理是通过接口实现的方式实现的。生成代理对象依靠两个核心的API.

  • InvocationHandler
每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用.
  • Proxy类
创建代理对象的类

首先需要定义动态代理类调用处理程序时候的处理逻辑:

package design.proxy_pattern.jdkdynamicproxy;

import design.proxy_pattern.Ticket;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.math.BigDecimal;

/**
 * @author pangjianfei
 * @date 19-5-22
 * @desc 动态代理类必须要实现InvocationHandler接口,通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由invoke方法执行
 */
public class RealUserInvocationHandler implements InvocationHandler {

    private Object realUser;

    public RealUserInvocationHandler(Object realUser) {
        this.realUser = realUser;
    }

    /**
     * 代理对象执行调用的时候,由该方法调用执行
     * @param o 代理对象
     * @param method 调用代理对象的某个方法的Method对象
     * @param objects 调用方法的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        System.out.println("代理对象开始计算方法执行时长");
        //如果要对不同的方法进行不同的增强,那么就需要判断method的类别,然后进行对应的处理
        if (method.getName().equals("takeTrain")) {
            Ticket ticket = (Ticket) objects[0];
            System.out.println(o.getClass().getName() + " : 去买从" + ticket.from + "到" + ticket.to + "的火车票了");
            Ticket backHomeTicket = new Ticket();
            backHomeTicket.setFrom(ticket.from);
            backHomeTicket.setTo(ticket.to);
            backHomeTicket.setTakeOffDate(ticket.takeOffDate);
            backHomeTicket.setRealPrice(new BigDecimal(100));
            backHomeTicket.setProxyPrice(new BigDecimal(120));
            //向下执行
            method.invoke(realUser, backHomeTicket);
        } else {
            method.invoke(realUser, objects);
        }
        return null;
    }
}

然后在程序的入口处创建代理对象执行:

/**
 * @author pangjianfei
 * @Date 2019/5/22
 * @desc
 */
public class Main {

    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        ticket.setFrom("北京");
        ticket.setTo("张家口");
        ticket.setTakeOffDate("2019-12-12");

        RealUser realUser = new RealUser();
        RealUserInvocationHandler invocationHandler = new RealUserInvocationHandler(realUser);
        //动态的生成一个代理对象,代理对象也是实现可realUser.getClass().getInterfaces()接口
        IUserDo proxyUser = (IUserDo) Proxy.newProxyInstance(realUser.getClass().getClassLoader(), realUser.getClass().getInterfaces(), invocationHandler);
        proxyUser.takeTrain(ticket);
//        proxyUser.goHome();

        //测试一个动态代理可以提供多种类型的数据使用, 最核心的其实就是 InvocationHandler在某些情况下可以复用
        AnotherTypeImpl anotherType = new AnotherTypeImpl();
        RealUserInvocationHandler invocationHandler1 = new RealUserInvocationHandler(anotherType);
        AnotherType proxyObject = (AnotherType) Proxy.newProxyInstance(anotherType.getClass().getClassLoader(), anotherType.getClass().getInterfaces(), invocationHandler1);
        proxyObject.testSay();
        
    }
}

这样一看,在这种情况下,其实动态代理并没有比静态代理少写多少代码?但是这只是在只有一个真实对象的前提下。
静态代理一个代理类只能代理一种类型(实现了相同接口的所有类),但是不能代理实现了不同接口的其他类型。
例如:

/**
 * 为了测试动态代理可以代理不同的类型定义的新的数据类型
 */
public interface AnotherType {

    public void testSay();
}

//上面是接口下面是实现类
package design.proxy_pattern.jdkdynamicproxy;
/**
 * @author pangjianfei
 * @Date 2019/5/23
 * @desc 另一种类型的真实对象
 */
public class AnotherTypeImpl implements AnotherType {
    @Override
    public void testSay() {
        System.out.println("另一种类型的真实对象说你好");
    }
}

那基于JDK的动态代理底层的实现原理是怎么样的呢?接着看一下代码?通过newProxyInstance这个怎么就会生成一个代理对象呢?
探究一下Proxy.newProxyInstance这个底层的实现:

/**
 * 精简之后的源码
 */
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        //获取应用程序的安全管理
        final SecurityManager sm = System.getSecurityManager();
        //如果有安全校验,进行check
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
        //获取代理对象的Class对象
        Class<?> cl = getProxyClass0(loader, intfs);

        /**获取构造方法,参数类型是InvocationHandler*/
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        //构建代理对象
        return cons.newInstance(new Object[]{h});
    }

Class<?> cl = getProxyClass0(loader, intfs);这个是如何实现的?

 private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }
    //如果在缓存中存在,那么从缓存中获取,否则使用代理类工厂创建一个
    return proxyClassCache.get(loader, interfaces);
}
//继续往下追,已经到了我的盲区了,需要一定的时间
基于CGLIB实现的动态代理
他是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承方式实现代理。(ASM是一个直接操作字节码文件的库,ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为)。

CGLIB实现动态代理必须手动的引入DGLIB的包。使用CGLIB实现动态代理依赖,同样也是依赖两个很重要的类。

  • MethodInterceptor
直观的理解为方法拦截器,MethodIntercepto继承了Callback接口,被代理对象的方法执行都会回调MethodInterceptor拦截,在intercept()中实现自己的处理逻辑。他的功能类似与JDK代理中的InvocationHandler
  • Enhancer
Enhancer是cglib中一个使用非常频繁的类,他是一个字节码增强器,可以用来为没有实现接口的类创建代理类。和Proxy的作用有点类似,但是实现的原理又不同。他可以根据给定的类创建子类,并且所有的非final方法都带有回调的钩子。(因为final修饰的方法不能被重写)

一、定义一个真实类

/**
 * @author pangjianfei
 * @Date 2019/5/23
 * @desc 定义一个真实的角色
 * 这个类没有实现任何的接口,所以无法使用JDK的动态代理来生成代理类进行增强操作
 */
public class RealPerson {
    public void sayHello() {
        System.out.println("真实的用户角色说:hello");
    }
}

二、定义一个方法拦截回调的类

/**
 * @author pangjianfei
 * @Date 2019/5/23
 * @desc 方法拦截,方法调用会被转发到该类的intercept()方法
 * 使用CGLIB动态代理的核心之一,类似于JDK动态代理中的InvocationHandler,MethodInterceptor继承了Callback,具有回调的特性
 */
public class UserMethodInterceptor implements MethodInterceptor {

    /**
     * 代理类执行方法调用的处理流程
     * @param o cglib生成的代理对象
     * @param method 被代理对象的方法
     * @param objects 被代理对象的方法的参数
     * @param methodProxy 要触发父类的方法对象
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("before: o的类型名称" + o.getClass().getName());
        System.out.println("method:method的名称" + method.getName());
//        System.out.println("objects: 对象的名称" + objects[0].toString());
        System.out.println("methodProxy : " + methodProxy.getSignature().getName());
        methodProxy.invokeSuper(o, objects);
        System.out.println("after: 方法执行完成");
        return null;
    }
}

三、入口

/**
 * @author pangjianfei
 * @Date 2019/5/23
 * @desc
 */
public class Main {

    public static void main(String[] args) {
        //添加系统的属性,将代理类class文件存入到本地的目录
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/pangjianfei/study/java/improve/dailytest/dailytest/target/classes");
        //Enhancer类是cglib中使用频繁的一个类,他是一个字节码增强器,可以用来为无接口的类创建代理。和Proxy类有点类似。
        //他可以根据给定的类创建子类,并且所有的非final方法都带有回调的钩子
        Enhancer enhancer = new Enhancer();
        //设置字节码增强器要创建的代理类的父类(基于继承实现)
        enhancer.setSuperclass(RealPerson.class);
        //设置非final方法的回调对象
        enhancer.setCallback(new UserMethodInterceptor());
        //创建一个代理对象
        RealPerson proxyPerson = (RealPerson) enhancer.create();
        proxyPerson.sayHello();
    }
}

以上是我们对CGLIB实现动态代理的实例以及简单的了解,我们在前面说了CGLIB是基于字节码操作生成的代理对象,创建的对象是被代理类的子类,所以经常说CGLIB是基于继承实现的,那么他到底是如何生成的子类呢。

代理模式中代理对象的具体创建流程已经是我的知识盲区了,这块不怎么了解,对于Java字节码的格式也不是很了解,所以具体的创建流程等我研究清楚之后回来补齐,请大家持续关注。 整理这一篇文章花费了好长时间,整理不易,请大家关注指教


如果觉得我的文章对你有用,请随意赞赏

你可能感兴趣的

载入中...