欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-06-17 19:49:48
...

前言:设计模式源于生活

什么是代理模式

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

为什么要使用代理模式

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

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

代理模式实现原理

浅谈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();
    }
}

运行结果图:

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

基于继承模式实现方式

主题:

/**
 * 主题
 */
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();
    }
}

运行效果图:

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

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();
    }
}

效果图:

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

原理分析:

添加代码,获取jdk自动生成的calss文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
浅谈Java【代理设计模式】以及原理解刨
1.使用反编译工具打开该proxy.class文件

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

浅谈Java【代理设计模式】以及原理解刨
2.分析生成的代理类,反射的自定义方法

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

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

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

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

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

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

以上就是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");
    }
}

效果图

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

源码分析

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

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

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

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

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

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

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

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

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

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

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

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

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

fastClass基本概念

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

入口进入

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

执行真正的目标对象方法

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

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