代理模式几大问题?

代理模式参考链接

  • 代理模式是什么?
    为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。来源百度百科。
    那么我的理解代理模式呢,就是我们不直接和具体的对象打交道,而是让代理对象和具体的对象打交道。
  • 为什么要使用代理模式?
    这个问题可以理解为使用代理对象有什么好处,主要的好处之一就是方便拓展。职责清晰,代理类提供访问接口,业务类只用负责业务实现
    代理类只负责提供使用接口供外部访问,而真正的实现由具体的类完成,具体的类修改了代码,对使用者来说是无感的(透明的)。
  • 代理模式在什么时候适用?
    当我们需要给某个类,当然不一定是类,进行功能拓展时,此时就可以使用代理模式来完成,比如为了安全,我们需要给某个具体的业务类 添加权限验证,那么使用代理模式解决是一个比较好的方案,如果将代码全写在业务类,则违背了单一职责原则,同时也不方便系统进行拓展。
  • 如何实现代理模式?
    代理模式的使用时比较简单的,核心思想就是使用一个类暴露接口给外部访问,然后在其基础上进行业务增强,由代理类完成具体的业务逻辑即可。

那么我们解决了这几大问题之后,我们大概就知道了代理模式是什么意思,主要应用场景是什么,那么最重要的是什么呢,就是代理模式是用来进行功能增强的,每次遇到功能增强的案例,就需要想到代理模式。

代理模式的分类

代理类在程序运行前就已经存在,代理类所实现的接口和所代理的方法都被固定,这种代理方式被称为静态代理。
  静态代理会存在这样的缺点:如果Subject(目标接口)中方法发生变化,那么真实类和代理类都需要跟着变化;如果需要被代理的真实类有很多,就需要相应的创建很多个代理类。
  那么,有没有一种机制能够让系统在运行时动态创建代理类,并可以对多个真实类进行代理操作?有,这就是我们将要介绍的这种动态代理(Dynamic Proxy)。

  • 静态代理:静态代理的意思是,在编译代码的时候类之间的代理关系就已经确定了,如果需要添加新的被代理对象需要重新修改代码,代理关系都写死在代码里面的就叫做静态代理
  • 动态代理:与之对应的就是动态代理,在代码的运行期间根据,传入的参数不同,而确定不同的被代理对象,在运行期间确定的,能够比较方便的进行拓展。

一个概念:在java中,遇到静态的,就可以理解为在编译时以及完成了的内容,就是静态的,而在运行期,根据代码执行情况完成的内容就叫做动态。

静态代理比较简单,一般使用不是很多,而动态代理在各个框架中广泛使用,那么我们来看看在java中如何使用动态代理。

动态代理的实现

基于java的动态代理实现有两种形式。

  • JDK动态代理:基于jdk的动态代理是基于接口实现的,如果被代理对象没有实现接口则不能完成代理
  • cglib动态代理:前面介绍了JDK动态代理的局限性,它要求被代理类必须实现一个接口。而CGLIB动态代理则没有此类强制性要求。CGLib动态代理是代理类去继承目标类,然后重写目标类的方法。既然是继承,则被代理类不能为final,并且 final 和static 修饰的方法也不会被拦截。

使用jdk中的原生api完成动态代理。
首先我们先介绍完成动态代理类的两个核心api,第一个是
java.lang.reflect.Proxy,此类的功能是帮助我们创建代理类的对象。我们给其传递相应的参数,其就会为我们创建好相应的代理对象。第二个类是:
import java.lang.reflect.InvocationHandler,此类的功能是,确定我们通过哪个接口为被代理对象创建代理对象,构成一种映射关系,在其中的invock方法中,通过反射调用被代理对象的方法和需要增强的功能。
在这里插入图片描述
用上面这张图来说明代理类和被代理对象之间的关系,代理类和被代理对象都实现了同样的接口,这样被代理对象就有了和被代理对象同样的方法。
分析代理对象的创建过程,从图中我们可以得到代理对象的创建传递了被代理对象实现的接口,所以其具有和被代理对象相同的方法,那么代理对象是如何调用被代理对象的方法的呢,可以看到还有一个参数我们没有使用,就是handler这个属性。那我们接着看handler这个对象。
在这里插入图片描述
在这里插入图片描述
从图中我们可以得出代理类只是调用了handler对象的invoke方法就完成了对被代理对象的代理过程。代理类中的每一个方法实现都是调用的同样的代码,也就是handler.invoke()。那么我们就需要分析handler对象的invoke方法到底做了什么。
分析invoke函数

///演示代码
public class AddServiceInvocationHandler implements InvocationHandler {
    private AddInterface addInterface;//此对象是被代理对象

    public AddServiceInvocationHandler(AddInterface addInterface) {
        this.addInterface = addInterface;
    }

    /*
     * @Description:
     * @Author: zdonliu
     * @Date: 2022/9/25 12:44 下午
      * @param proxy 动态代理生成的代理类
      * @param method 被代理对象调用的方法
      * param args 被代理对象调用方法传递的参数
     **/

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object invoke = method.invoke(addInterface, args);
        System.out.println("  >>> 发送消息通知, 执行方法名称 :: " + method.getName());
        return invoke;
    }
}

invoke函数的第一个参数是代理对象,因为每个代理对象都会对一个具体的类进行代理,这个类是实现了被代理接口的类,不是具体的类,主要是为了标识这个信息,method 这个参数是代理对象调用的方法,通过这个参数识别调用的方法,然后通过反射指定被代理对象的方法执行,args就是被代理对象调用函数的参数,然后在method.invoke()方法调用的前后进行代码书写就能够对方法进行功能增强了。
通过分析这个过程我们可以很清楚的得出使用jdk自带的API进行动态代理高度依赖接口,而且只能针对一个接口进行动态代理类的创建,只能代理实现了这个接口的类对象,而且是这个接口的方法,对接口高度绑定。同时我们知道代理对象并不是真正完成功能增强的对象,只是进行了转发,交给handler完成的功能增强。

Java中动态代理使用与原理详解
Java API 之 动态代理
Java设计模式及应用场景之《代理模式》

public interface AddInterface {
    boolean add(String obj);
    boolean sub(String obj);
}
//只能对 AddInterface接口进行功能增强
public class AddInterfaceImpl implements AddInterface, OtherInterface {
    public boolean add(String obj){
        System.out.println("添加元素"+obj.toString());
        return true;
    }

    @Override
    public boolean sub(String obj) {
        System.out.println("删除元素"+obj.toString());
        return true;
    }

    @Override
    public boolean otherMethod(String other) {
        System.out.println("其他元素"+other.toString());
        return true;
    }
}

public class proxyTest {
    public static void main(String[] args) {
        AddInterface addInterface =  new AddInterfaceImpl();
        AddServiceInvocationHandler  handler =new AddServiceInvocationHandler(addInterface);
        AddInterface addInterfaceProxy = (AddInterface) Proxy
                .newProxyInstance(addInterface.getClass().getClassLoader(), addInterface.getClass().getInterfaces(), handler);
        addInterfaceProxy.sub("djlfk ");

        addInterfaceProxy.add("jdlfakj");

    }
}

public class AddServiceInvocationHandler implements InvocationHandler {
    private AddInterface addInterface;//此对象是被代理对象

    public AddServiceInvocationHandler(AddInterface addInterface) {
        this.addInterface = addInterface;
    }

    /*
     * @Description:
     * @Author: zdonliu
     * @Date: 2022/9/25 12:44 下午
      * @param proxy 动态代理生成的代理类
      * @param method 被代理对象调用的方法
      * param args 被代理对象调用方法传递的参数
     **/

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object invoke = method.invoke(addInterface, args);
        System.out.println("  >>> 发送消息通知, 执行方法名称 :: " + method.getName());
        return invoke;
    }
}

基于CGLIB完成动态代理

使用jdk提供的API完成动态代理和接口高度绑定,如果类没有实现接口是不能完成动态代理的,而且只能对接口里面的方法完成代理,那么针对其他的类就需要使用另一种方式完成代理了,于是催生出cglib动态代理的技术,cglib完成动态代理的原理和基于jdk的动态代理类似,同样需要两个类,一个类完成代理对象的创建,另一个类完成功能增强,和被代理对象的业务方法调用,只不过基于cglib的动态代理是依赖于继承实现的,通过创建一个被代理对象的子类,来完成被代理对象的创建。
分析两个类,第一个类:
net.sf.cglib.proxy.Enhancer,这个类的功能同jdk的Proxy类功能类似,完成代理类的创建。
分析代码可以看出其需要的参数和proxy类创建代理类的参数类似。

public static Object getProxy(Class<?> clazz) {
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"./");
        // 创建动态代理增强类
        Enhancer enhancer = new Enhancer();
        // 设置类加载器
        enhancer.setClassLoader(clazz.getClassLoader());
        // 设置被代理类
        enhancer.setSuperclass(clazz);
        // 设置方法拦截器
        enhancer.setCallback(new DebugMethodInterceptor());
        // 创建代理类
        return enhancer.create();
    }

第二个重要类是实现了MethodInterceptor接口的类,这个接口是一个规范,定义了拦截器的书写规范,每个代理类都通过调用拦截器的方法,完成代理过程。

    /**
            * @param o           代理对象
            * @param method      被拦截的方法(需要增强的方法)
            * @param objects        方法入参
   * @param methodProxy 原始方法函数描述类
   */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method " + method.getName()+o.getClass().getName());
        Object object = methodProxy.invokeSuper(o, objects);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method " + method.getName());
        return object;
    }

我们重写的intercept方法是一个规范,所有的代理对象都是调用这个方法,传递的参数都是固定的格式,通过这个例子我们也可以理解接口就是一个规则这句话的意思了。就是必须按照这个规范来写,才能完成相应的功能,因为在其他位置的代码是按照接口的规范进行的方法调用。
通过查看生产的代理对象,我们可以看出,cglib给我们创建好的代理对象通过重写了被代理对象的方法,然后调用通用的拦截器代码,完成的代理操作。

    public final String send(String var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null ? (String)var10000.intercept(this, CGLIB$send$0$Method, new Object[]{var1}, CGLIB$send$0$Proxy) : super.send(var1);
    }

缺点:
CGLIB 的最大不足在于,它需要继承我们的委托类,所以如果委托类被修饰为 final,那就意味着,这个类 CGLIB 代理不了。
自然的,即便某个类不是 final 类,但是其中如果有 final 修饰的方法,那么该方法也是不能被代理的。这一点从我们反射的源码可以看出来,CGLIB 生成的代理类需要重写委托类中所有的方法,而一个修饰为 final 的方法是不允许重写的。
基于 CGLIB 库的动态代理机制
Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)


版权声明:本文为aslzd123原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/aslzd123/article/details/127027980