【设计模式-结构型】代理模式

一、什么是代理模式

        在港片中,经常能看到一些酷炫的大哥被警察抓了,警察会试图从他们口中套出一些关键信息。但这些大哥们通常会非常冷静地回应:“我有权保持沉默,我要找我的律师。” 

        这个律师就像是大哥的“法律盾牌”,全权处理所有法律事务。律师的角色不仅仅是代理大哥发言,更是在法律的战场上为大哥披荆斩棘。具体来说,律师会做以下几件事情:

  1. 准备法律文件:律师会精心准备各种法律文件,确保每一份文件都无懈可击,为大哥的辩护打下坚实的基础。

  2. 与检察官沟通:律师会与检察官进行多轮沟通,争取对大哥最有利的条件,甚至可能会在法庭上展开一场激烈的辩论。

  3. 法庭辩护:在法庭上,律师会代表大哥发言,用专业的法律知识和丰富的经验,为大哥进行有力的辩护,确保大哥的权益不受侵犯。

  4. 提供法律建议:律师还会向大哥提供专业的法律建议,帮助他了解自己的权利和义务,让他在法律的框架内做出最明智的决定。

        这个过程就像是一场精心策划的战役,律师作为代理,不仅代理了大哥的发言,还在操作执行前后添加了诸多增强处理,确保大哥在法律的保护下安然无恙。这种场景在港片中屡见不鲜,不仅展示了法律的严肃性,还增添了一丝戏剧性和紧张感,让人看得津津有味。

       港片中的大哥,就是运用代理模式的行家能手。下面来说代理模式,代理模式是一种结构型的设计模式,代理对象(律师)具备真实对象(犯事大哥)的功能,并代替真实对象完成相应操作(律师代表大哥讲话),同时可以在操作执行前后进行增强处理(准备法律文件、与检察官沟通等)。

二、为什么用代理模式

        通过一个形象的例子我们知道了代理模式,通过这个例子我们可以聊一下为什么要用代理模式(请律师):

  • 权限控制(保护目标对象):保护客户的隐私和权益,避免不当操作。律师作为代理,可以控制犯罪嫌疑人与外界的交流,确保所有信息的传递都在法律框架内进行。例如,律师可以决定哪些信息可以透露给检察官,哪些信息需要保密。

  • 优化性能(提高效率):代理模式可以实现资源的延迟加载和缓存,提高资源利用效率,减少不必要的资源消耗。律师在需要时才会准备具体的法律文件,而不是一开始就进行大量的准备工作。例如,只有在确定案件进入法庭审理阶段时,律师才会准备详细的辩护词。

  • 增加功能(减轻目标对象的负担):代理模式可以在代理对象中添加额外的功能,而不需要修改原始对象的代码。

  • 简化客户端代码:客户端可以更专注于核心业务,而不必处理复杂的细节。犯罪嫌疑人只需要与律师沟通,而不需要直接处理复杂的法律事务。

三、代理模式示例

代理对象分为2类分别是静态代理和动态代理

3.1 静态代理

        静态代理需要我们自己去实现代理模式。下面以请律师为demo实现一个静态代理的示例。从静态代理的代码中可以发现,静态代理的缺点显而易见,那就是当真实类的方法越来越多的时候,这样构建的代理类的代码量是非常大的,所以就引进动态代理。

1、定义法律援助接口

public interface LegalService {
    void representClient();
}

2、目标对象(大哥) 

public class Suspect implements LegalService {
    @Override
    public void representClient() {
        System.out.println("我是大哥,我需要律师为我进行辩护.");
    }
}

3、实现代理(律师)

public class LawyerProxy implements LegalService {
    private Suspect suspect;

    public LawyerProxy(Suspect suspect) {
        this.suspect = suspect;
    }

    @Override
    public void representClient() {
        prepareLegalDocuments();
        communicateWithProsecutor();
        suspect.representClient();
        provideLegalAdvice();
    }

    private void prepareLegalDocuments() {
        System.out.println("准备法律文件.");
    }

    private void communicateWithProsecutor() {
        System.out.println("与检察官进行战略沟通,协商有利条件.");
    }

    private void provideLegalAdvice() {
        System.out.println("为嫌疑人提供专业法律建议,确保其权利得到保护.");
    }
}

4、 场景

public class Main {
    public static void main(String[] args) {
        Suspect suspect = new Suspect();
        LegalService lawyer = new LawyerProxy(suspect);
        lawyer.representClient();
    }
}

//输出场景内容
准备法律文件.
与检察官进行战略沟通,协商有利条件.
我是大哥,我需要律师为我进行辩护.
为嫌疑人提供专业法律建议,确保其权利得到保护.

3.2 动态代理

        动态代理是开源工具提供给我们实现好的工具可以直接使用(动态代理允许使用一种方法的单个类(代理类)为具有任意数量方法的任意类(真实类)的多个方法调用提供服务。其实运用的是反射机制)。目前来说接触到的动态代理主要有2种分别对应的是jdk实现的动态代理 和 cglib动态代理。

3.2.1 jdk动态代理(接口代理

3.2.1.1 jdk相关类和核心方法简单描述

        jdk动态代理主要使用的是 java.lang.reflect包。

        核心类InvocationHandler接口    Proxy类

        核心方法:invoke newProxyInstance

public Object invoke(Object proxy,Method method,Object[] args) throws Throwable

public static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)throws IllegalArgumentException
 3.2.1.2 jdk动态代理代码示例  

法律援助接口和目标对象(大哥)不用变使用静态代理的代码,其他进行修改。

1、实现InvocationHandler ,作为代理处理器

InvocationHandler,这个处理器将拦截对真实对象的调用,并在调用前后添加额外的逻辑。

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

public class LawyerProxyHandler implements InvocationHandler {
    private final LegalService realLawyer;

    public LawyerProxyHandler(LegalService realLawyer) {
        this.realLawyer = realLawyer;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在调用真实方法之前执行的逻辑
        preHandle(args);

        // 调用真实方法
        Object result = method.invoke(realLawyer, args);

        // 在调用真实方法之后执行的逻辑
        postHandle(args);

        return result;
    }

    private void preHandle(Object[] args) {
        System.out.println("律师正在准备法律文件和证据.");

    }

    private void postHandle(Object[] args) {
        System.out.println("律师已完成对委托人的代理.");
    }
}

2、创建具体代理对象(律师)

import java.lang.reflect.Proxy;

public class LegalServiceProxy {
    public static LegalService getProxyInstance(LegalService realLawyer) {
        return (LegalService) Proxy.newProxyInstance(
            realLawyer.getClass().getClassLoader(), // 目标类的类加载器
            new Class[]{LegalService.class}, // 目标类实现的接口
            new LawyerProxyHandler(realLawyer) // 代理处理器
        );
    }
}

3、使用场景

public class Main {
    public static void main(String[] args) {
        // 创建真实对象
        LegalService dg = new RealLawyer();

        // 创建代理对象
        LegalService lawyerProxy = LegalServiceProxy.getProxyInstance(dg);

        // 通过代理对象调用方法
        lawyerProxy.representClient();
    }
}

//输出
律师正在准备法律文件和证据.
我是大哥,我需要律师为我进行辩护.
律师已完成对委托人的代理.

3.2.2 cglib动态代理(继承代理

        从上面可以看出,jdk动态代理的前提条件是,要有接口存在,那还有许多场景是没有接口的,这个时候就需要cglib动态代理了,CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承方式实现代理。

1、大哥类要改一下,因为没有接口

public class Suspect{
    @Override
    public void representClient() {
        System.out.println("我是大哥,我需要律师为我进行辩护.");
    }
}

2、 实现MethodInterceptor,作为代理处理器

实现MethodInterceptor接口,这个拦截器将拦截对真实对象的调用,并在调用前后添加额外的逻辑。

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class LawyerProxyInterceptor implements MethodInterceptor {
    private final Suspect suspect;

    public LawyerProxyInterceptor(Suspect suspect) {
        this.suspect = suspect;
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 在调用真实方法之前执行的逻辑
        preHandle(args);

        // 调用真实方法
        Object result = proxy.invokeSuper(obj, args);

        // 在调用真实方法之后执行的逻辑
        postHandle(args);

        return result;
    }

     private void preHandle(Object[] args) {
        System.out.println("律师正在准备法律文件和证据.");

    }

    private void postHandle(Object[] args) {
        System.out.println("律师已完成对委托人的代理.");
    }
}

3、 创建具体代理对象(律师)

        使用CGLIB的Enhancer类创建代理对象。这个代理对象将代理真实对象,并在调用方法时通过MethodInterceptor进行拦截。

import net.sf.cglib.proxy.Enhancer;

public class LegalServiceProxy {
    public static RealLawyer getProxyInstance(Suspect suspect) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(suspect.getClass()); // 设置目标类
        enhancer.setCallback(new LawyerProxyInterceptor(suspect)); // 设置回调
        return (Suspect) enhancer.create(); // 创建代理对象
    }
}

4、使用场景 

public class Main {
    public static void main(String[] args) {
        // 创建真实对象
        Suspect suspect = new Suspect();

        // 创建代理对象
        Suspect lawyerProxy = LegalServiceProxy.getProxyInstance(suspect);

        // 通过代理对象调用方法
        lawyerProxy.representClient();
    }
}


//输出
律师正在准备法律文件和证据.
我是大哥,我需要律师为我进行辩护.
律师已完成对委托人的代理.

3.2.3 动态代理总结

JDK动态代理和CGLIB动态代理是Java中实现动态代理的两种常用机制,虽然它们都可以为目标对象创建代理对象并拦截方法调用,但它们的工作原理和使用场景有所不同。以下是它们的区别:

1. 基于接口 vs 基于类继承
  • JDK动态代理

    • 基于接口:JDK动态代理要求被代理的类必须实现一个或多个接口。如果类没有实现任何接口,JDK动态代理将无法工作。

    • 优点:实现简单,使用Java内置API、无需依赖第三方库。

    • 缺点:只能代理接口,不能代理普通类。方法调用时使用反射,性能相对较低。

  • CGLIB动态代理

    • 基于类集成:CGLIB动态代理通过生成目标类的子类来实现代理。它不要求目标类必须实现接口,因此它适用于没有实现接口的类。

    • 优点:可以代理没有接口的类。方法调用性能较高,避免了反射调用。

    • 缺点:创建代理类时需要进行字节码操作,性能开销较大。需要依赖cglib和ASM库。

2. 实现机制
  • JDK动态代理

    • 使用反射机制,通过 java.lang.reflect.Proxy 类和 InvocationHandler 接口来实现代理。代理对象仅代理接口中的方法。

    • 当调用代理对象的方法时,代理类会拦截方法调用,并通过 InvocationHandler.invoke() 方法执行额外的逻辑。

  • CGLIB动态代理

    • 基于字节码操作,使用 CGLIB(Code Generation Library)生成目标类的子类并重写目标类的方法来实现代理。通过继承方式拦截所有非 final 方法的调用。

    • CGLIB 使用的是 ASM 字节码生成框架,生成的是字节码级别的代理类,因此性能相对较好,但生成代理类的开销比JDK动态代理略大。

3. 性能差异
  • JDK动态代理

    • 对于实现了接口的类来说,JDK动态代理在创建代理对象时开销较小,因为它仅依赖反射机制来处理接口方法的调用。

    • 对于频繁调用代理方法的场景,JDK动态代理可能比CGLIB略慢,因为每次调用都涉及反射。

  • CGLIB动态代理

    • 由于CGLIB是通过字节码生成来创建代理类,生成代理类的开销比JDK动态代理高一些,尤其是在代理类较多的情况下。

    • 但CGLIB代理的实际方法调用性能更高,因为它通过字节码操作,减少了反射调用的开销。

4. 使用场景
  • JDK动态代理

    • 适用于接口驱动的编程,如果目标类实现了接口,那么使用JDK动态代理是首选方式。

    • 适合在不需要对类进行直接代理的场景,通常在应用中,业务逻辑往往是通过接口定义的,因此JDK代理在实际项目中更常用。

  • CGLIB动态代理

    • 适用于没有实现接口的类,例如一些现有类或者第三方库的类没有提供接口的情况下,可以使用CGLIB动态代理。

    • 适用于对类进行代理时,但需要注意类不能是 final,否则CGLIB无法生成代理子类。

5. Spring AOP中的使用
  • JDK动态代理

    • 在Spring AOP中,如果目标对象实现了接口,Spring默认使用JDK动态代理。这是因为Spring AOP的核心思想是基于接口的面向切面编程(Aspect-Oriented Programming)。

  • CGLIB动态代理

    • 如果目标对象没有实现任何接口,Spring AOP会自动使用CGLIB动态代理。在Spring配置中,你也可以强制使用CGLIB代理(通过设置 proxyTargetClass=true)。

你可能感兴趣的:(设计模式,代理模式)