浅谈Java【代理设计模式】以及原理解刨

时间:2022-07-24
本文章向大家介绍浅谈Java【代理设计模式】以及原理解刨,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

前言:设计模式源于生活

什么是代理模式

为其他对象提供一种代理,控制对这个对象的访问 白话文:为某个对象实现动态增强

为什么要使用代理模式

中介隔离:在某些情况下,一个客户类不想或不能直接引用一个委托对象,而代理类对象可以在客户类与委托类之间起到中介的作用,其特征代理类 与委托类实现的是相同接口

开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

代理模式实现原理

代理模式主要包含三种角色,有抽象角色,委托角色、代理角色 抽象角色:可以是接口,也可以是抽象类 委托角色:真实主题角色,具体业务逻辑执行的地方 代理角色:里面包含了真实主题角色引用,负责对真实主题角色执行前或后进行操作处理

代理模式应用场景

日志收集 SpringAop 动态事务开关 全局捕获异常 过滤器 RPC远程调用

代理模式的分类

静态代理和动态代理模式

静态代理模式

静态代理是由程序员手动创建或工具生成代理类的源码,再编译成代理类。 所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。 一句话,自己手写代理类就是静态代理。

静态代理的缺陷

基于人工手写代理类,后期被代理类的增多,代理类也会随之增多

基于接口模式实现方式

主题:

/**
 * 主题
 */
public interface OrderService {

    void addOrder();

    void updateOrder();
}

实现接口:

/**
* 被代理类
*/
public class OrderServiceImpl implements OrderService {

   public void addOrder() {
       System.out.println("执行新增业务逻辑方法");
   }

   public void updateOrder() {
       System.out.println("执行修改业务逻辑方法");
   }
}

代理类:

/**
 * 代理类
 * <p>
 * 缺点:每次有新的接口,代理类也就需要加入一个方法,造成代码的冗余性了
 * 优点:解耦合了,因为只需要关注业务逻辑代码,不需要关心其他的操作
 */
public class OrderServiceProxy implements OrderService {

    private OrderServiceImpl orderService;

    public OrderServiceProxy(OrderServiceImpl orderService) {
        this.orderService = orderService;
    }


    public void addOrder() {
        System.out.println("开启事务");
        orderService.addOrder();
        System.out.println("提交事务");
    }

    public void updateOrder() {
        System.out.println("开启事务");
        orderService.updateOrder();
        System.out.println("提交事务");
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        OrderService orderServiceProxy = new OrderServiceProxy(new OrderServiceImpl());
        orderServiceProxy.addOrder();
        System.out.println("--");
        orderServiceProxy.updateOrder();
    }
}

运行结果图:

基于继承模式实现方式

主题:

/**
 * 主题
 */
public interface OrderService {

    void addOrder();

    void updateOrder();
}

实现接口:

/**
 * 被代理类
 */
public class OrderServiceImpl implements OrderService {

    public void addOrder() {
        System.out.println("执行新增业务逻辑方法");
    }

    public void updateOrder() {
        System.out.println("执行修改业务逻辑方法");
    }
}

代理类:

public class OrderProxy extends OrderServiceImpl {

    @Override
    public void addOrder() {
        System.out.println("开启事务");
        super.addOrder();
        System.out.println("提交事务");
    }

    @Override
    public void updateOrder() {
        System.out.println("开启事务");
        super.updateOrder();
        System.out.println("提交事务");
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        OrderProxy orderProxy = new OrderProxy();
        orderProxy.addOrder();
        System.out.println("--");
        orderProxy.updateOrder();
    }
}

运行效果图:

jdk动态代理模式

jdk动态代理执行步骤: 1.创建被代理类,和接口类 2.通过实现invcation接口来调用proxy方法,实现动态创建代理类的实例 简单来说:动态代理:通过程序动态生成代理,无需手工添加

主题:

public interface OrderService {

    void addOrder();
}

实现接口:

public class OrderServiceImpl implements OrderService {

    @Override
    public void addOrder() {
        System.out.println("执行新增订单逻辑");
    }
}

jdk动态代理:

public class JdkInvocationHandler implements InvocationHandler {

    private Object target;

    /**
     * 目标对象-被代理类
     *
     * @param target
     */
    public JdkInvocationHandler(Object target) {
        this.target = target;
    }

    /**
     * @param proxy  jdk动态代理生成的代理类
     * @param method 接口中的方法哦(不是真正目标方法)
     * @param args   代理的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("jdk动态代理开始执行");

        //调用目标方法--java反射执行目标方法
        Object invoke = method.invoke(target, args);

        System.out.println("jdk动态代理结束执行");
        return invoke;
    }

    public <T> T getProxy() {
        /**
         *  三个参数
         *  ClassLoader loader,  读取代理类class文件  -- 类加载器
         *  Class<?>[] interfaces 基于该接口拼成代理类源代码
         *  InvocationHandler h  就是this
         */
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        JdkInvocationHandler jdkInvocationHandler = new JdkInvocationHandler(new OrderServiceImpl());
        OrderService proxy = jdkInvocationHandler.getProxy();
        proxy.addOrder();
    }
}

效果图:

原理分析:

添加代码,获取jdk自动生成的calss文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
1.使用反编译工具打开该proxy.class文件

通过生成的代理类都是以$Proxy开头,0是第一个生成的代理类,代理类会被很多业务代理进行调用,所以这里的0是通过自增的形式,防止代理类重复

2.分析生成的代理类,反射的自定义方法

首先了解一下invoke的三个参数的意义 第一个:代理类 对号入座:本身就是代理类,所以这里就是this 第二个:被代理类 对号入座:m3就是通过反射获取被代理类的接口和方法等 第三个:参数 对号入座:我这里就没参数了 所以null

上图不是我们真正执行代理类的地方,它是通过super关键字通过调用父类的回调类,执行我们自定义配置代理类,进去瞧一波,接着走到下一步

首先从我们注释上可以了解到,method并不是我们真正的目标方法,而target才是,可能有人会疑问,那么target是从哪里传进来的,可以看我图中所标记的地方,在类中定义了一个全局变量,通过构造方法的形式,将外部对象的引用传递给我们类中的全局变量,并通过类中的invoke方法执行我们的真正的目标对象,在执行目标之前我们是已经完成了对对象的预处理和末尾处理

以上就是jdk动态代理执行原理

注意:由于java不能实现多继承,这里已经继承了Proxy类,所以不能在继承其他的类了,所以jdk动态代理只支持接口代理,不支持继承实现类的代理

cglib动态代理

jdk动态代理与cglib动态代理的区别

jdk动态代理:通过走回调拦截,实现接口生成的代理类,使用反射执行目标方法 原理: 1.拼接java源代码 2.将java源代码编译为class文件 3.通过类加载器读取class文件到内存中 4.采用java的反射机制执行目标方法

cglib动态代理:采用继承模式生成代理类(相当于直接重写被代理方法,不使用反射),底层基于ASN字节码技术实现 原理: 1.直接采用ASN字节码技术生成class文件 2.通过内加载器读取class文件到内存中 3.采用fastClass索引机制执行目标对象方法,比反射机制效率高

得出CGlib的效率比jdk动态代理效率高

主题

public interface OrderService {

    void saveOrder(String name);
}

实现类

public class OrderServiceImpl implements OrderService {

    public void saveOrder(String name) {
        System.out.println("执行添加订单业务逻辑" + name);
    }
}

cglib代理类

public class CGLibMethodIntercetor implements MethodInterceptor {


    /**
     * @param o cglib生成好的代理对象
     * @param method 目标方法
     * @param objects 参数
     * @param methodProxy 代理
     * @return
     * @throws Throwable
     */
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("开启事务");
        Object invoke = methodProxy.invokeSuper(o, objects);
        System.out.println("提交事务");
        return invoke;
    }
}

测试类

public class Test {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(OrderServiceImpl.class); //这里其实用到了被代理类的引用
        enhancer.setCallback(new CGLibMethodIntercetor()); //这里是拦截回调,简单说就是在执行真正目标方法之前和之后进行额外的增强

        //创建代理对象
        OrderService o = (OrderService) enhancer.create();
        o.saveOrder("computer");
    }
}

效果图

源码分析

添加代码,输出class文件到指定目录
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\code");

一、首先执行完了之后,指定目录会有这三个class文件 第一个文件是cglib的索引文件,能够快速找到代理类 第二个是代理类啦 第三个是fastClass索引文件

打开反编译工具,打开代理类的class文件,可以看到class文件是通过继承的形式来实现代理的

然后接着可以看到,saveOrder是我们需要被代理的方法,在进入目标对象之前,会先执行Method接口,这个接口是直接指定到我自定义的cglib动态代理类,然后会执行增强处理,然后再执行目标对象

可能这里会有人好奇,引用怎么设置进来的,就是通过我setCallback方法设置设置进来

进入save方法,程序会先拿到被代理类的引用,当被代理类引用不为NULL的时候,会通过intercept方法,执行代理 类

在执行真正目标方法之前,先增强,开启事务,然后在执行目标对象

fastClass基本概念

相当于对类中的所有方法生成一个索引值,直接根据索引调用方法

入口进入

底层会自动拦截回调到执行intercept方法,实现对目标方法的增强,然后会通过methodProxy.invokeSuper,执行调用真实目标方法

进来之后,会先初始化FastClass对象,在进入看一下,就会明白了

初始化方法,fastClassInfo对象是否为空,如果为空则进行初始化,继续探索一下fastCalssInfo对象里面到底有啥

FastClass f1:对代理对象索引 FastClass f2:代理类的索引 int i1:对代理对象的索引值 int i2:代理类的索引值

现在知道了对象里面有哪些属性之后,在接着往回看到,第一次加载进入初始化方法,会将FastClass对象各个属性进行赋值,f1 和 f2 属性 也就是代理对象和被代理对象的引用,那么i1.getIndex就是通过方法名称加参数类型进行签名,得出的索引值,i2的index值也是同理获取

getIndex其实就是通过对方法名称和参数类型进行签名然后得出HashCode值,通过switch找到相应的hashcode值返回最终的索引值出去

然后初始化完成之后,可以看到下图代理类的i2索引值是19

通过索引值去生成的代理FastClass类找到19的索引值,然后可以看到返回的是我们的saveOrder方法

通过方法名称,再去生成的代理类里面找到代理类生成的方法名称,通过super回调直接找到目标真正目标方法

如下图,找到写的目标真正方法类

执行真正的目标对象方法

以上就是cglib代理类源码分析.